Skip to content

fft: add irfft as the inverse real FFT (fixes #176) - #261

Merged
sbryngelson merged 2 commits into
sbryngelson:mainfrom
axiom-of-choice:feat/irfft
Sep 2, 2026
Merged

fft: add irfft as the inverse real FFT (fixes #176)#261
sbryngelson merged 2 commits into
sbryngelson:mainfrom
axiom-of-choice:feat/irfft

Conversation

@axiom-of-choice

Copy link
Copy Markdown
Contributor

Fixes #176. Adds aneforge.fft.irfft(X_re, X_im, N) as the inverse real FFT, the natural completion of the existing rfft.

Approach: thin wrapper over the existing ifft_plan machinery, as the issue suggests. Runs the on-engine inverse FFT over the Hermitian-symmetric spectrum and returns the real part. ifft already applies the 1/N normalization, and for a Hermitian spectrum the imaginary part is ~0 (numpy.fft.irfft also returns only the real part), so the real-signal contract holds with no new kernel or plan.

Changes

  • aneforge/fft.py: irfft function + export in __all__.
  • aneforge/fft.py _selftest: round-trip irfft(rfft(x)) ~= x across N=128 and N=512, plus a numpy.fft.irfft oracle over the half spectrum.
  • tests/test_fft.py (new, requires_ane like test_fft2.py): 8 tests covering fft/ifft/rfft/irfft against numpy, the round-trip, the numpy oracle, and that irfft returns a real array.

Checks run

  • On-device (M2 Pro / macOS): _selftest PASS, tests/test_fft.py 8 passed.
  • Off-device: pytest -m "not requires_ane" 407 passed, 2 skipped. ruff check, the pylint 2-space gate, pyright aneforge/fft.py tests/test_fft.py (0 errors), and compileall all clean.

CI runs the off-device subset; the transform itself dispatches, so the on-device _selftest and tests/test_fft.py were run locally and pass on M2 Pro.

@sbryngelson sbryngelson left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified on device (M5): all 8 tests in the new file pass. Thanks for adding tests/test_fft.py --
the FFT module had no pytest coverage at all before this.

Two small things before merge.

The docstring should say irfft takes the FULL length-N spectrum, the way this module's rfft
returns it, and not the N//2+1 half spectrum that numpy.fft.irfft expects. The name invites the
numpy reading, and the test itself has to slice Xr[:N // 2 + 1] to build its reference, so the
mismatch is already visible in the PR.

The new test file also has no trailing newline.

Both, as a patch:

diff --git a/aneforge/fft.py b/aneforge/fft.py
index 4350d40..d9cfb1c 100644
--- a/aneforge/fft.py
+++ b/aneforge/fft.py
@@ -292,7 +292,10 @@ def rfft(x_real, N: int):
 def irfft(X_re, X_im, N: int):
   """Inverse real FFT of a Hermitian-symmetric spectrum on the ANE; returns the real time-domain
   signal of length N (the imag part is ~0 by Hermitian symmetry, and numpy.fft.irfft also
-  returns only the real part)."""
+  returns only the real part).
+
+  Takes the FULL length-N spectrum, as `rfft` returns it -- not the N//2+1 half spectrum that
+  numpy.fft.irfft expects."""
   x_re, _ = ifft_plan(N)(X_re, X_im)
   return x_re
 
diff --git a/tests/test_fft.py b/tests/test_fft.py
index 666d9c3..2be98c5 100644
--- a/tests/test_fft.py
+++ b/tests/test_fft.py
@@ -60,4 +60,4 @@ def test_irfft_returns_real_only():
   x = rng.standard_normal(256).astype(np.float32)
   Xr, Xi = agfft.rfft(x, 256)
   back = agfft.irfft(Xr, Xi, 256)
-  assert np.iscomplexobj(np.asarray(back)) is False
\ No newline at end of file
+  assert np.iscomplexobj(np.asarray(back)) is False

@sbryngelson
sbryngelson merged commit c2ad830 into sbryngelson:main Sep 2, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fft: irfft (inverse real FFT)

2 participants