-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Put tests of FFT backends into TestUtils submodule #78
Merged
Merged
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
1c69f6c
Add TestUtils submodule/extension
gaurav-arya 68daedb
Fix typo
gaurav-arya c7f9b79
Support Julia 1.0
gaurav-arya babfca6
Add missing test deps
gaurav-arya 09d8383
Add adjoint testing to test utilities
gaurav-arya 06f8b91
Remove mul! method from inplace test plan (consistent with fftw)
gaurav-arya fe2793e
Fix typo
gaurav-arya 98fdcde
Document test utilities
gaurav-arya 3537f76
Apply code review suggestions and refactor TestUtils
gaurav-arya 73a419b
Support Julia 1.0
gaurav-arya 4d1fdb7
Reorder kwargs in doc string
gaurav-arya cc0fe9d
Also explicitly test AbstractFFTs.plan_inv
gaurav-arya 6da1cc3
Lift isdefined checks out of __init__
gaurav-arya 74b15cf
Update src/definitions.jl
gaurav-arya c5383e6
Note TestUtils is a weak extension
gaurav-arya 239129b
Update function names in error handler
gaurav-arya 18ee107
Add missing test_adjoint's for BRFFT, IRFFT
gaurav-arya e14c045
Collect x_rfft so as to not hit #112
gaurav-arya 6fec1b7
Merge branch 'master' into backend-tests
gaurav-arya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
# This file contains code that was formerly part of Julia. License is MIT: https://julialang.org/license | ||
|
||
module AbstractFFTsTestExt | ||
|
||
using AbstractFFTs | ||
using AbstractFFTs: TestUtils | ||
using AbstractFFTs.LinearAlgebra | ||
using Test | ||
|
||
# Ground truth x_fft computed using FFTW library | ||
const TEST_CASES = ( | ||
(; x = collect(1:7), dims = 1, | ||
x_fft = [28.0 + 0.0im, | ||
-3.5 + 7.267824888003178im, | ||
-3.5 + 2.7911568610884143im, | ||
-3.5 + 0.7988521603655248im, | ||
-3.5 - 0.7988521603655248im, | ||
-3.5 - 2.7911568610884143im, | ||
-3.5 - 7.267824888003178im]), | ||
(; x = collect(1:8), dims = 1, | ||
x_fft = [36.0 + 0.0im, | ||
-4.0 + 9.65685424949238im, | ||
-4.0 + 4.0im, | ||
-4.0 + 1.6568542494923806im, | ||
-4.0 + 0.0im, | ||
-4.0 - 1.6568542494923806im, | ||
-4.0 - 4.0im, | ||
-4.0 - 9.65685424949238im]), | ||
(; x = collect(reshape(1:8, 2, 4)), dims = 2, | ||
x_fft = [16.0+0.0im -4.0+4.0im -4.0+0.0im -4.0-4.0im; | ||
20.0+0.0im -4.0+4.0im -4.0+0.0im -4.0-4.0im]), | ||
(; x = collect(reshape(1:9, 3, 3)), dims = 2, | ||
x_fft = [12.0+0.0im -4.5+2.598076211353316im -4.5-2.598076211353316im; | ||
15.0+0.0im -4.5+2.598076211353316im -4.5-2.598076211353316im; | ||
18.0+0.0im -4.5+2.598076211353316im -4.5-2.598076211353316im]), | ||
(; x = collect(reshape(1:8, 2, 2, 2)), dims = 1:2, | ||
x_fft = cat([10.0 + 0.0im -4.0 + 0.0im; -2.0 + 0.0im 0.0 + 0.0im], | ||
[26.0 + 0.0im -4.0 + 0.0im; -2.0 + 0.0im 0.0 + 0.0im], | ||
dims=3)), | ||
(; x = collect(1:7) + im * collect(8:14), dims = 1, | ||
x_fft = [28.0 + 77.0im, | ||
-10.76782488800318 + 3.767824888003175im, | ||
-6.291156861088416 - 0.7088431389115883im, | ||
-4.298852160365525 - 2.7011478396344746im, | ||
-2.7011478396344764 - 4.298852160365524im, | ||
-0.7088431389115866 - 6.291156861088417im, | ||
3.767824888003177 - 10.76782488800318im]), | ||
(; x = collect(reshape(1:8, 2, 2, 2)) + im * reshape(9:16, 2, 2, 2), dims = 1:2, | ||
x_fft = cat([10.0 + 42.0im -4.0 - 4.0im; -2.0 - 2.0im 0.0 + 0.0im], | ||
[26.0 + 58.0im -4.0 - 4.0im; -2.0 - 2.0im 0.0 + 0.0im], | ||
dims=3)), | ||
) | ||
|
||
|
||
function TestUtils.test_plan(P::AbstractFFTs.Plan, x::AbstractArray, x_transformed::AbstractArray; inplace_plan=false, copy_input=false) | ||
_copy = copy_input ? copy : identity | ||
if !inplace_plan | ||
@test P * _copy(x) ≈ x_transformed | ||
@test P \ (P * _copy(x)) ≈ x | ||
_x_out = similar(P * _copy(x)) | ||
@test mul!(_x_out, P, _copy(x)) ≈ x_transformed | ||
@test _x_out ≈ x_transformed | ||
else | ||
_x = copy(x) | ||
@test P * _copy(_x) ≈ x_transformed | ||
@test _x ≈ x_transformed | ||
@test P \ _copy(_x) ≈ x | ||
@test _x ≈ x | ||
end | ||
end | ||
|
||
function TestUtils.test_plan_adjoint(P::AbstractFFTs.Plan, x::AbstractArray; real_plan=false, copy_input=false) | ||
_copy = copy_input ? copy : identity | ||
y = rand(eltype(P * _copy(x)), size(P * _copy(x))) | ||
# test basic properties | ||
@test_skip eltype(P') === typeof(y) # (AbstractFFTs.jl#110) | ||
@test (P')' === P # test adjoint of adjoint | ||
@test size(P') == AbstractFFTs.output_size(P) # test size of adjoint | ||
# test correctness of adjoint and its inverse via the dot test | ||
if !real_plan | ||
@test dot(y, P * _copy(x)) ≈ dot(P' * _copy(y), x) | ||
@test dot(y, P \ _copy(x)) ≈ dot(P' \ _copy(y), x) | ||
else | ||
_component_dot(x, y) = dot(real.(x), real.(y)) + dot(imag.(x), imag.(y)) | ||
@test _component_dot(y, P * _copy(x)) ≈ _component_dot(P' * _copy(y), x) | ||
@test _component_dot(x, P \ _copy(y)) ≈ _component_dot(P' \ _copy(x), y) | ||
end | ||
@test_throws MethodError mul!(x, P', y) | ||
end | ||
|
||
function TestUtils.test_complex_ffts(ArrayType=Array; test_inplace=true, test_adjoint=true) | ||
@testset "correctness of fft, bfft, ifft" begin | ||
for test_case in TEST_CASES | ||
_x, dims, _x_fft = copy(test_case.x), test_case.dims, copy(test_case.x_fft) | ||
x = convert(ArrayType, _x) # dummy array that will be passed to plans | ||
x_complexf = convert(ArrayType, complex.(float.(x))) # for testing mutating complex FFTs | ||
x_fft = convert(ArrayType, _x_fft) | ||
|
||
# FFT | ||
@test fft(x, dims) ≈ x_fft | ||
if test_inplace | ||
_x_complexf = copy(x_complexf) | ||
@test fft!(_x_complexf, dims) ≈ x_fft | ||
@test _x_complexf ≈ x_fft | ||
end | ||
# test OOP plans, checking plan_fft and also inv and plan_inv of plan_ifft, | ||
# which should give functionally identical plans | ||
for P in (plan_fft(similar(x_complexf), dims), | ||
(_inv(plan_ifft(similar(x_complexf), dims)) for _inv in (inv, AbstractFFTs.plan_inv))...) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_complexf, x_fft) | ||
if test_adjoint | ||
@test fftdims(P') == fftdims(P) | ||
TestUtils.test_plan_adjoint(P, x_complexf) | ||
end | ||
end | ||
if test_inplace | ||
# test IIP plans | ||
for P in (plan_fft!(similar(x_complexf), dims), | ||
(_inv(plan_ifft!(similar(x_complexf), dims)) for _inv in (inv, AbstractFFTs.plan_inv))...) | ||
TestUtils.test_plan(P, x_complexf, x_fft; inplace_plan=true) | ||
end | ||
end | ||
|
||
# BFFT | ||
x_scaled = prod(size(x, d) for d in dims) .* x | ||
@test bfft(x_fft, dims) ≈ x_scaled | ||
if test_inplace | ||
_x_fft = copy(x_fft) | ||
@test bfft!(_x_fft, dims) ≈ x_scaled | ||
@test _x_fft ≈ x_scaled | ||
end | ||
# test OOP plans. Just 1 plan to test, but we use a for loop for consistent style | ||
for P in (plan_bfft(similar(x_fft), dims),) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_fft, x_scaled) | ||
if test_adjoint | ||
TestUtils.test_plan_adjoint(P, x_fft) | ||
end | ||
end | ||
# test IIP plans | ||
for P in (plan_bfft!(similar(x_fft), dims),) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_fft, x_scaled; inplace_plan=true) | ||
end | ||
|
||
# IFFT | ||
@test ifft(x_fft, dims) ≈ x | ||
if test_inplace | ||
_x_fft = copy(x_fft) | ||
@test ifft!(_x_fft, dims) ≈ x | ||
@test _x_fft ≈ x | ||
end | ||
# test OOP plans | ||
for P in (plan_ifft(similar(x_complexf), dims), | ||
(_inv(plan_fft(similar(x_complexf), dims)) for _inv in (inv, AbstractFFTs.plan_inv))...) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_fft, x) | ||
if test_adjoint | ||
TestUtils.test_plan_adjoint(P, x_fft) | ||
end | ||
end | ||
# test IIP plans | ||
if test_inplace | ||
for P in (plan_ifft!(similar(x_complexf), dims), | ||
(_inv(plan_fft!(similar(x_complexf), dims)) for _inv in (inv, AbstractFFTs.plan_inv))...) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_fft, x; inplace_plan=true) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
function TestUtils.test_real_ffts(ArrayType=Array; test_adjoint=true, copy_input=false) | ||
@testset "correctness of rfft, brfft, irfft" begin | ||
for test_case in TEST_CASES | ||
_x, dims, _x_fft = copy(test_case.x), test_case.dims, copy(test_case.x_fft) | ||
x = convert(ArrayType, _x) # dummy array that will be passed to plans | ||
x_real = float.(x) # for testing mutating real FFTs | ||
x_fft = convert(ArrayType, _x_fft) | ||
x_rfft = selectdim(x_fft, first(dims), 1:(size(x_fft, first(dims)) ÷ 2 + 1)) | ||
|
||
if !(eltype(x) <: Real) | ||
continue | ||
end | ||
|
||
# RFFT | ||
@test rfft(x, dims) ≈ x_rfft | ||
for P in (plan_rfft(similar(x_real), dims), | ||
(_inv(plan_irfft(similar(x_rfft), size(x, first(dims)), dims)) for _inv in (inv, AbstractFFTs.plan_inv))...) | ||
@test eltype(P) <: Real | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_real, x_rfft; copy_input=copy_input) | ||
if test_adjoint | ||
TestUtils.test_plan_adjoint(P, x_real; real_plan=true, copy_input=copy_input) | ||
end | ||
end | ||
|
||
# BRFFT | ||
x_scaled = prod(size(x, d) for d in dims) .* x | ||
@test brfft(x_rfft, size(x, first(dims)), dims) ≈ x_scaled | ||
for P in (plan_brfft(similar(x_rfft), size(x, first(dims)), dims),) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_rfft, x_scaled; copy_input=copy_input) | ||
end | ||
|
||
# IRFFT | ||
@test irfft(x_rfft, size(x, first(dims)), dims) ≈ x | ||
for P in (plan_irfft(similar(x_rfft), size(x, first(dims)), dims), | ||
(_inv(plan_rfft(similar(x_real), dims)) for _inv in (inv, AbstractFFTs.plan_inv))...) | ||
@test eltype(P) <: Complex | ||
@test fftdims(P) == dims | ||
TestUtils.test_plan(P, x_rfft, x; copy_input=copy_input) | ||
end | ||
end | ||
end | ||
end | ||
|
||
end |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
module TestUtils | ||
|
||
""" | ||
TestUtils.test_complex_ffts(ArrayType=Array; test_inplace=true, test_adjoint=true) | ||
|
||
Run tests to verify correctness of FFT, BFFT, and IFFT functionality using a particular backend plan implementation. | ||
The backend implementation is assumed to be loaded prior to calling this function. | ||
|
||
# Arguments | ||
|
||
- `ArrayType`: determines the `AbstractArray` implementation for | ||
which the correctness tests are run. Arrays are constructed via | ||
`convert(ArrayType, ...)`. | ||
- `test_inplace=true`: whether to test in-place plans. | ||
- `test_adjoint=true`: whether to test [plan adjoints](api.md#Base.adjoint). | ||
""" | ||
function test_complex_ffts end | ||
|
||
""" | ||
TestUtils.test_real_ffts(ArrayType=Array; test_adjoint=true, copy_input=false) | ||
|
||
Run tests to verify correctness of RFFT, BRFFT, and IRFFT functionality using a particular backend plan implementation. | ||
The backend implementation is assumed to be loaded prior to calling this function. | ||
|
||
# Arguments | ||
|
||
- `ArrayType`: determines the `AbstractArray` implementation for | ||
which the correctness tests are run. Arrays are constructed via | ||
`convert(ArrayType, ...)`. | ||
- `test_adjoint=true`: whether to test [plan adjoints](api.md#Base.adjoint). | ||
- `copy_input=false`: whether to copy the input before applying the plan in tests, to accomodate for | ||
[input-mutating behaviour of real FFTW plans](https://github.com/JuliaMath/AbstractFFTs.jl/issues/101). | ||
""" | ||
function test_real_ffts end | ||
|
||
# Always copy input before application due to FFTW real plans possibly mutating input (AbstractFFTs.jl#101) | ||
""" | ||
TestUtils.test_plan(P::Plan, x::AbstractArray, x_transformed::AbstractArray; | ||
inplace_plan=false, copy_input=false) | ||
|
||
Test basic properties of a plan `P` given an input array `x` and expected output `x_transformed`. | ||
|
||
Because [real FFTW plans may mutate their input in some cases](https://github.com/JuliaMath/AbstractFFTs.jl/issues/101), | ||
we allow specifying `copy_input=true` to allow for this behaviour in tests by copying the input before applying the plan. | ||
""" | ||
function test_plan end | ||
|
||
""" | ||
TestUtils.test_plan_adjoint(P::Plan, x::AbstractArray; real_plan=false, copy_input=false) | ||
|
||
Test basic properties of the [adjoint](api.md#Base.adjoint) `P'` of a particular plan given an input array `x`, | ||
including its accuracy via the dot test. | ||
|
||
Real-to-complex and complex-to-real plans require a slightly modified dot test, in which case `real_plan=true` should be provided. | ||
The plan is assumed out-of-place, as adjoints are not yet supported for in-place plans. | ||
Because [real FFTW plans may mutate their input in some cases](https://github.com/JuliaMath/AbstractFFTs.jl/issues/101), | ||
we allow specifying `copy_input=true` to allow for this behaviour in tests by copying the input before applying the plan. | ||
""" | ||
function test_plan_adjoint end | ||
|
||
function __init__() | ||
if isdefined(Base, :get_extension) && isdefined(Base.Experimental, :register_error_hint) | ||
devmotion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Better error message if users forget to load Test | ||
Base.Experimental.register_error_hint(MethodError) do io, exc, _, _ | ||
if (exc.f === test_real_fft || exc.f === test_complex_fft) && Base.get_extension(AbstractFFTs, :AbstractFFTsTestExt) === nothing | ||
print(io, "\nDid you forget to load Test?") | ||
end | ||
end | ||
end | ||
end | ||
|
||
end |
This file contains 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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can wrap it inside a
if isdefined(Base, :get_extension)
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outside of the whole
__init__
function I meant, not in its function body.