Inverse plans (inv, \, ldiv!) and in-place plans (plan_fft!, plan_bfft!) - #133
Open
pankgeorg wants to merge 7 commits into
Open
Inverse plans (inv, \, ldiv!) and in-place plans (plan_fft!, plan_bfft!)#133pankgeorg wants to merge 7 commits into
pankgeorg wants to merge 7 commits into
Conversation
With FFTW.jl loaded alongside FFTA, plan_rfft(::Vector{Float64}, ::Int) was
ambiguous between FFTW's StridedArray method and FFTA's method annotated
with region::RegionTypes, turning rfft(x) into a MethodError. Leave region
unannotated on the AbstractFFTs entry points (as plan_fft already does) and
normalise it in an internal function, so FFTW's methods are strictly more
specific and take over as AbstractFFTs intends.
A coexistence test runs in a subprocess (loading FFTW in the test process
would make every other test exercise FFTW).
Real-input/real-output plans only implemented *, so every rfft/irfft allocated, mul!(y, p, x) with a preallocated output was a MethodError, rfft along one dimension of an N-d array went through mapslices, and 2D real plans ran a full complex transform and discarded half of it. FFTAPlan_re now carries a scratch buffer and two pencil kernels (_rfft_pencil!/_brfft_pencil!) implement the even-length half-size trick and the odd-length transform on AbstractVectors, so views work as input and output. mul! is defined for 1D plans on 1D and N-d arrays (looping over pencils along the region) and for 2D plans on N-d arrays (real transform along the first region dimension, complex along the second), and * allocates the output and calls mul!. The 2D plan's first call graph is built for the half length like the 1D plan's.
inv(p) threw a TypeError for every FFTA plan: no plan_inv method existed
and the dummy pinv::FFTAInvPlan field made AbstractFFTs' pinv_type
resolve to Union{}. The plan structs are now mutable with an initially
undefined pinv field, as in FFTW.jl, so AbstractFFTs.inv caches the
result of the new plan_inv methods for complex and real plans; p \ x,
ldiv!(y, p, x), plan_ifft and plan_irfft work through them.
plan_fft!/plan_bfft! return an FFTAPlan_inplace wrapping an ordinary plan
plus a buffer: when input and output alias, the input is copied to the
buffer and transformed out of place (FFTA's kernels are out of place, and
the 1D pencil path is not alias-safe); otherwise the wrapped plan is used
directly. fft!, bfft! and ifft! from AbstractFFTs now work — the internal
kernel that shadowed AbstractFFTs.fft! is renamed fft_kernel!.
…llow AbstractFFTs backend hooks in the ExplicitImports check
…e the other AbstractFFTs entry points
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #133 +/- ##
==========================================
- Coverage 98.80% 98.17% -0.64%
==========================================
Files 5 4 -1
Lines 585 656 +71
==========================================
+ Hits 578 644 +66
- Misses 7 12 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Aug 29, 2026
…nels Replacing mapslices with strided views made real transforms along dims=2 of wide matrices (64 x N) 1.2-1.3x slower on x86-64: the mapslices copy had been an unlabelled copy-in that turned a stride-of-a-cache-line gather into one contiguous pass before the kernel. Pencils whose parent arrays are unit-stride along the transform dimension still go to the kernels directly (the dims=1 gain stays); any other pencil is copied to two plan-owned contiguous buffers first and copied back after, so execution stays allocation-free and the dims=2 result is now identical to the mapslices one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item from the plan in #130 that came out of checking what DSP.jl needs from a plan (stacked on #132; the diff includes its commit until it lands).
Before
inv(p)threwTypeError: in typeassert, expected Union{}for every FFTA plan: there was noplan_invmethod, and the placeholderpinv::FFTAInvPlanfield madeAbstractFFTs.pinv_typeresolve toUnion{}. Consequentlyp \ x,ldiv!(y, p, x)andinv(plan_fft!(x))(used by DSP.jl's convolution) were unavailable.plan_fft!,plan_bfft!,plan_ifft!, and thereforefft!,bfft!,ifft!, had no methods — andfft!(x)could never have worked, because FFTA's internal kernel was itself namedfft!, shadowingAbstractFFTs.fft!in the exported namespace.Now
mutablewith aconstpayload and an initially undefinedpinvfield, exactly the pattern FFTW.jl uses, soAbstractFFTs.invcachesplan_inv(p)in the plan.plan_invis defined for complex plans (opposite direction,1/prod(size)scale) and real plans (rfft↔brfft).plan_fft!/plan_bfft!return anFFTAPlan_inplacewrapping an ordinary plan and a buffer shaped like the input. When the input and output alias, the input is copied to the buffer and transformed out of place (FFTA's kernels are out of place; the 1D pencil path is not alias-safe);mul!(y, p!, x)with distinct arrays uses the wrapped plan directly.plan_ifft!andfft!/bfft!/ifft!follow from AbstractFFTs.fft_kernel!so the AbstractFFTs function is no longer shadowed.test/inverse_inplace.jlcoversinv,\,ldiv!,plan_ifft/plan_irfft, caching, in-place plans for 1D and N-d regions,fft!/bfft!/ifft!, allocation-free in-place execution for 1D plans, and the DSP.jl patternp! = plan_fft!(x); ip! = inv(p!); p! * buf; ip! * buf.plan_inv,ScaledPlanandnormalization(non-public AbstractFFTs names that a backend has to use, likePlan), and the Aqua piracy allowance coversplan_fft!/plan_bfft!like the other entry points.