1use crate::infer::GraphExt as _;
33use crate::op::Activation;
34use crate::{DType, Graph, NodeId, Op, Shape, fft::FftNorm};
35
36impl Graph {
37 pub fn hilbert(&mut self, x: NodeId) -> (NodeId, NodeId) {
40 let shape = self.shape(x).clone();
41 let last = shape.rank() - 1;
42 let l = shape.dim(last).unwrap_static();
43 let n = crate::fft::next_pow2(l);
44
45 let (re, im) = self.fft_real(x, FftNorm::Backward); let mut h = vec![0f32; n];
50 h[0] = 1.0;
51 if n >= 2 {
52 h[n / 2] = 1.0;
53 for hk in h.iter_mut().take(n / 2).skip(1) {
54 *hk = 2.0;
55 }
56 }
57 let h_node = self.const_f32_tensor(h, &[n]);
58 let re_h = self.mul(re, h_node);
59 let im_h = self.mul(im, h_node);
60
61 let block = self.concat_(vec![re_h, im_h], last);
63 let full = self.fft_norm(block, true, FftNorm::Forward); let a_re = self.narrow_(full, last, 0, n);
65 let a_im = self.narrow_(full, last, n, n);
66 (
68 self.narrow_(a_re, last, 0, l),
69 self.narrow_(a_im, last, 0, l),
70 )
71 }
72
73 pub fn envelope(&mut self, x: NodeId) -> NodeId {
75 let (re, im) = self.hilbert(x);
76 self.complex_abs(re, im)
77 }
78
79 pub fn instantaneous_phase(&mut self, x: NodeId) -> NodeId {
85 let (re, im) = self.hilbert(x);
86 let r = self.complex_abs(re, im);
87 let denom = self.add(r, re);
88 let eps = self.constant(1e-12, DType::F32);
89 let denom = self.add(denom, eps);
90 let ratio = self.div(im, denom);
91 let s = crate::shape::unary_shape(self.shape(ratio));
92 let at = self.activation(Activation::Atan, ratio, s);
93 let two = self.constant(2.0, DType::F32);
94 self.mul(at, two)
95 }
96
97 pub fn fir_filtfilt(&mut self, x: NodeId, taps: &[f32]) -> NodeId {
104 let k = taps.len();
105 assert!(k > 0, "fir_filtfilt: need ≥1 tap");
106 let last = self.shape(x).rank() - 1;
107 let l = self.shape(x).dim(last).unwrap_static();
108 let h = self.const_f32_tensor(taps.to_vec(), &[k]);
109 let y1 = self.fir_conv_same(x, h, k, l);
110 let y2 = self.reverse(y1, vec![last]);
111 let y3 = self.fir_conv_same(y2, h, k, l);
112 self.reverse(y3, vec![last])
113 }
114
115 fn fir_conv_same(&mut self, x: NodeId, taps: NodeId, k: usize, l: usize) -> NodeId {
121 let full = self.fft_conv1d(x, taps, 0, FftNorm::Forward); let start = (k - 1) / 2;
123 let last = self.shape(full).rank() - 1;
124 self.narrow_(full, last, start, l)
125 }
126
127 pub fn biquad(&mut self, x: NodeId, b: [f32; 3], a: [f32; 3]) -> NodeId {
136 assert!(a[0] != 0.0, "biquad: a0 must be non-zero");
137 let (b0, b1, b2) = (b[0] / a[0], b[1] / a[0], b[2] / a[0]);
138 let (a1, a2) = (a[1] / a[0], a[2] / a[0]);
139
140 let xs_shape = self.shape(x).clone();
141 let orig_dims: Vec<i64> = xs_shape
142 .dims()
143 .iter()
144 .map(|d| d.unwrap_static() as i64)
145 .collect();
146 let rank = xs_shape.rank();
147 let n = xs_shape.dim(rank - 1).unwrap_static();
148 let p: usize = (0..rank - 1)
149 .map(|i| xs_shape.dim(i).unwrap_static())
150 .product();
151
152 let x_pn = self.reshape_(x, vec![p as i64, n as i64]);
154 let xs = self.transpose_(x_pn, vec![1, 0]); let mut body = Graph::new("biquad_body");
158 let carry = body.input("carry", Shape::new(&[p, 3], DType::F32));
159 let x_t = body.input("x_t", Shape::new(&[p], DType::F32));
160 let z1p = body.narrow_(carry, 1, 1, 1);
161 let z1p = body.reshape_(z1p, vec![p as i64]);
162 let z2p = body.narrow_(carry, 1, 2, 1);
163 let z2p = body.reshape_(z2p, vec![p as i64]);
164 let cb0 = body.constant(b0 as f64, DType::F32);
165 let cb1 = body.constant(b1 as f64, DType::F32);
166 let cb2 = body.constant(b2 as f64, DType::F32);
167 let ca1 = body.constant(a1 as f64, DType::F32);
168 let ca2 = body.constant(a2 as f64, DType::F32);
169 let b0x = body.mul(x_t, cb0);
170 let y = body.add(b0x, z1p); let b1x = body.mul(x_t, cb1);
172 let a1y = body.mul(y, ca1);
173 let z1_tmp = body.sub(b1x, a1y);
174 let z1 = body.add(z1_tmp, z2p); let b2x = body.mul(x_t, cb2);
176 let a2y = body.mul(y, ca2);
177 let z2 = body.sub(b2x, a2y); let yc = body.reshape_(y, vec![p as i64, 1]);
179 let z1c = body.reshape_(z1, vec![p as i64, 1]);
180 let z2c = body.reshape_(z2, vec![p as i64, 1]);
181 let next = body.concat_(vec![yc, z1c, z2c], 1); body.set_outputs(vec![next]);
183
184 let init = self.const_f32_tensor(vec![0.0; p * 3], &[p, 3]);
186 let traj = self.add_node(
187 Op::Scan {
188 body: Box::new(body),
189 length: n as u32,
190 save_trajectory: true,
191 num_bcast: 0,
192 num_xs: 1,
193 num_checkpoints: 0,
194 },
195 vec![init, xs],
196 Shape::new(&[n, p, 3], DType::F32),
197 );
198 let y_np = self.narrow_(traj, 2, 0, 1); let y_np = self.reshape_(y_np, vec![n as i64, p as i64]);
201 let y_pn = self.transpose_(y_np, vec![1, 0]); self.reshape_(y_pn, orig_dims)
203 }
204
205 pub fn sosfilt(&mut self, x: NodeId, sections: &[([f32; 3], [f32; 3])]) -> NodeId {
208 let mut y = x;
209 for &(b, a) in sections {
210 y = self.biquad(y, b, a);
211 }
212 y
213 }
214
215 pub fn sosfiltfilt(&mut self, x: NodeId, sections: &[([f32; 3], [f32; 3])]) -> NodeId {
219 let last = self.shape(x).rank() - 1;
220 let f1 = self.sosfilt(x, sections);
221 let r1 = self.reverse(f1, vec![last]);
222 let f2 = self.sosfilt(r1, sections);
223 self.reverse(f2, vec![last])
224 }
225
226 pub fn resample_poly(&mut self, x: NodeId, up: usize, down: usize, taps: &[f32]) -> NodeId {
234 assert!(up >= 1 && down >= 1, "resample_poly: up/down must be ≥1");
235 assert!(!taps.is_empty(), "resample_poly: need ≥1 tap");
236 let shape = self.shape(x).clone();
237 let rank = shape.rank();
238 let last = rank - 1;
239 let n = shape.dim(last).unwrap_static();
240 let lead: Vec<i64> = (0..last)
241 .map(|i| shape.dim(i).unwrap_static() as i64)
242 .collect();
243
244 let up_sig = if up == 1 {
246 x
247 } else {
248 let mut d1: Vec<i64> = lead.clone();
249 d1.push(n as i64);
250 d1.push(1);
251 let xr = self.reshape_(x, d1);
252 let mut zdims: Vec<usize> = shape
253 .dims()
254 .iter()
255 .take(last)
256 .map(|d| d.unwrap_static())
257 .collect();
258 zdims.push(n);
259 zdims.push(up - 1);
260 let zeros = self.const_f32_tensor(vec![0.0; zdims.iter().product()], &zdims);
261 let stacked = self.concat_(vec![xr, zeros], last + 1); let mut flat: Vec<i64> = lead.clone();
263 flat.push((n * up) as i64);
264 self.reshape_(stacked, flat)
265 };
266
267 let l = n * up;
269 let scaled: Vec<f32> = taps.iter().map(|&t| t * up as f32).collect();
270 let h = self.const_f32_tensor(scaled, &[taps.len()]);
271 let filtered = self.fir_conv_same(up_sig, h, taps.len(), l);
272
273 if down == 1 {
276 return filtered;
277 }
278 let out_len = l.div_ceil(down);
279 let padded = out_len * down;
280 let filtered = if padded > l {
281 let mut zdims: Vec<usize> = shape
282 .dims()
283 .iter()
284 .take(last)
285 .map(|d| d.unwrap_static())
286 .collect();
287 zdims.push(padded - l);
288 let zeros = self.const_f32_tensor(vec![0.0; zdims.iter().product()], &zdims);
289 self.concat_(vec![filtered, zeros], last)
290 } else {
291 filtered
292 };
293 let mut rdims: Vec<i64> = lead.clone();
294 rdims.push(out_len as i64);
295 rdims.push(down as i64);
296 let reshaped = self.reshape_(filtered, rdims); let col0 = self.narrow_(reshaped, last + 1, 0, 1); let mut odims: Vec<i64> = lead;
299 odims.push(out_len as i64);
300 self.reshape_(col0, odims)
301 }
302
303 fn complex_abs(&mut self, re: NodeId, im: NodeId) -> NodeId {
304 let re2 = self.mul(re, re);
305 let im2 = self.mul(im, im);
306 let sum = self.add(re2, im2);
307 self.sqrt(sum)
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
316 g.shape(id)
317 .dims()
318 .iter()
319 .map(|d| d.unwrap_static())
320 .collect()
321 }
322 fn input(g: &mut Graph, shape: &[usize]) -> NodeId {
323 g.input("x", Shape::new(shape, DType::F32))
324 }
325
326 #[test]
327 fn hilbert_preserves_shape() {
328 let mut g = Graph::new("hil");
329 let x = input(&mut g, &[3, 128]);
330 let (re, im) = g.hilbert(x);
331 assert_eq!(dims(&g, re), vec![3, 128]);
332 assert_eq!(dims(&g, im), vec![3, 128]);
333 }
334
335 #[test]
336 fn envelope_and_phase_shape() {
337 let mut g = Graph::new("env");
338 let x = input(&mut g, &[2, 100]);
339 let e = g.envelope(x);
340 let p = g.instantaneous_phase(x);
341 assert_eq!(dims(&g, e), vec![2, 100]);
342 assert_eq!(dims(&g, p), vec![2, 100]);
343 }
344
345 #[test]
346 fn filtfilt_same_length() {
347 let mut g = Graph::new("ff");
348 let x = input(&mut g, &[2, 200]);
349 let taps: Vec<f32> = (0..15).map(|i| 1.0 / (i as f32 + 1.0)).collect();
350 let y = g.fir_filtfilt(x, &taps);
351 assert_eq!(dims(&g, y), vec![2, 200]);
352 }
353
354 #[test]
355 fn biquad_and_sosfilt_same_length() {
356 let mut g = Graph::new("iir");
357 let x = input(&mut g, &[3, 128]);
358 let b = [0.2, 0.4, 0.2];
359 let a = [1.0, -0.3, 0.1];
360 let y = g.biquad(x, b, a);
361 assert_eq!(dims(&g, y), vec![3, 128]);
362 let sos = [(b, a), ([0.5, 0.0, 0.5], [1.0, 0.2, 0.05])];
363 let z = g.sosfiltfilt(x, &sos);
364 assert_eq!(dims(&g, z), vec![3, 128]);
365 }
366
367 #[test]
368 fn resample_poly_length() {
369 let mut g = Graph::new("rs");
370 let x = input(&mut g, &[2, 100]);
371 let taps: Vec<f32> = (0..13).map(|i| 1.0 / (i as f32 + 1.0)).collect();
372 let y = g.resample_poly(x, 3, 2, &taps);
374 assert_eq!(dims(&g, y), vec![2, 150]);
375 }
376}