hipFFTW examples#
hipFFTW is the FFTW3-compatible interface shipped with
hipFFT. The routine
names, planner flags and calling sequence are those of FFTW3, so existing FFTW
code moves across with little change. hipFORT exposes it through the
hipfort_hipfftw module.
The one difference that matters: the in and out arguments are
device pointers. FFTW declares them void*, so a pointer from
hipMalloc passes straight through, but host arrays do not work.
Every program on this page is a complete, self-contained example that is built
and run as part of the hipFORT test suite. The tests live in
test/f2003/hipfftw. Unlike the other FFT libraries there is no Fortran 2008
variant, because the FFTW API is pointer-based throughout and gains nothing
from Fortran array pointers.
For the cuFFT-style interface to the same library, see hipFFT examples.
Transform workflow#
A hipFFTW transform follows the FFTW3 sequence:
Allocate device memory with
hipMalloc, or host-accessible memory withfftw_alloc_realandfftw_alloc_complex.Build a plan with
fftw_plan_dft_1d,fftw_plan_dft_r2c_1d,fftw_plan_many_dftorfftw_plan_guru_dft.Run it with the matching
fftw_execute_dft,fftw_execute_dft_r2corfftw_execute_dft_c2r.Release the plan with
fftw_destroy_plan.
Keep the following conventions in mind:
FFTW transforms are unnormalized. A forward transform followed by an inverse transform of length
NreturnsNtimes the original data.Planner flags are the standard FFTW values.
FFTW_ESTIMATEis not emitted by the generated enums module, so declare it yourself asinteger(c_int), parameter :: FFTW_ESTIMATE = 64.Real forward transforms produce Hermitian-symmetric output, so only
N/2 + 1complex values are stored.Multi-dimensional transforms use C row-major order, so the last dimension is contiguous.
The double precision routines are named
fftw_*and the single precision onesfftwf_*.
Building an example#
The examples only need the hipfftw and hip hipFORT components:
find_package(hipfort REQUIRED COMPONENTS hip hipfftw)
add_executable(my_fft hipfftw_c2c.f03)
target_link_libraries(my_fft PRIVATE hipfort::hipfftw hipfort::hip)
See Using hipFORT in your application for the full set of build options.
Complex-to-complex transform#
A one-dimensional complex-to-complex transform. The plan is built with
fftw_plan_dft_1d over two device pointers and executed once. The input is
a sum of two harmonics, so the output has energy in exactly two bins.
program hipfftw_c2c_test
use iso_c_binding
use hipfort
use hipfort_check
use hipfort_hipfftw
implicit none
integer(c_int), parameter :: N = 16
integer(c_size_t), parameter :: Nbytes = N * 16 ! sizeof(double complex) = 16
double precision, parameter :: pi = 4.0d0 * atan(1.0d0)
double precision, parameter :: tol = 1.0d-12
! FFTW_ESTIMATE is not emitted by the generated enums module; use the
! standard FFTW planner-flag value.
integer(c_int), parameter :: FFTW_ESTIMATE = 64
complex(c_double_complex), allocatable, target, dimension(:) :: hx, hresult
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer :: j
double precision :: error, max_error
complex(c_double_complex) :: w
write(*,"(a)",advance="no") "-- Running test 'hipfftw_c2c' (Fortran 2003) - "
! Signal: x[j] = exp(2*pi*i*1*j/N) + 2*exp(2*pi*i*5*j/N)
! Expected DFT: X[1]=N, X[5]=2*N, all other bins zero (0-indexed)
w = cmplx(0.0d0, 2.0d0 * pi / dble(N), kind=c_double_complex)
allocate(hx(N), hresult(N))
do j = 0, N-1
hx(j+1) = exp(w * dble(j)) + 2.0d0 * exp(5.0d0 * w * dble(j))
end do
call hipCheck(hipMalloc(dx, Nbytes))
call hipCheck(hipMalloc(dy, Nbytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), Nbytes, hipMemcpyHostToDevice))
! Device pointers pass directly to the FFTW C API (in/out are void*).
plan = fftw_plan_dft_1d(N, dx, dy, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult(1)), dy, Nbytes, hipMemcpyDeviceToHost))
max_error = 0.0d0
do j = 0, N-1
if (j == 1) then
error = abs(hresult(j+1) - cmplx(dble(N), 0.0d0, kind=c_double_complex))
else if (j == 5) then
error = abs(hresult(j+1) - cmplx(2.0d0 * N, 0.0d0, kind=c_double_complex))
else
error = abs(hresult(j+1))
end if
max_error = max(max_error, error)
end do
if (max_error > tol) then
write(*,*) "FAILED! max error = ", max_error
call exit(1)
end if
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
deallocate(hx, hresult)
write(*,*) "PASSED!"
end program hipfftw_c2c_test
Real-to-complex and complex-to-real transforms#
fftw_plan_dft_r2c_1d and fftw_plan_dft_c2r_1d build the two halves of a
real round trip. The complex side holds N/2 + 1 elements.
program hipfftw_r2c_c2r_test
use iso_c_binding
use hipfort
use hipfort_check
use hipfort_hipfftw
implicit none
integer(c_int), parameter :: N = 16
integer(c_int), parameter :: Nc = N / 2 + 1
integer(c_size_t), parameter :: Nbytes_r = N * 8 ! sizeof(double) = 8
integer(c_size_t), parameter :: Nbytes_c = Nc * 16 ! sizeof(double complex) = 16
double precision, parameter :: pi = 4.0d0 * atan(1.0d0)
double precision, parameter :: tol = 1.0d-12
! FFTW_ESTIMATE is not emitted by the generated enums module; use the
! standard FFTW planner-flag value.
integer(c_int), parameter :: FFTW_ESTIMATE = 64
real(c_double), allocatable, target, dimension(:) :: hx, hresult_r
complex(c_double_complex), allocatable, target, dimension(:) :: hresult_c
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr, dz = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer :: j
double precision :: error, max_error
write(*,"(a)",advance="no") "-- Running test 'hipfftw_r2c_c2r' (Fortran 2003) - "
! Real signal: x[j] = 1 + 2*cos(2*pi*j/N) + 3*cos(2*pi*3*j/N)
! R2C output (Nc = N/2+1 complex values, 0-indexed):
! X[0] = N, X[1] = N, X[3] = 3*N/2, rest zero
allocate(hx(N), hresult_c(Nc), hresult_r(N))
do j = 0, N-1
hx(j+1) = 1.0d0 + 2.0d0 * cos(2.0d0 * pi * j / dble(N)) &
+ 3.0d0 * cos(6.0d0 * pi * j / dble(N))
end do
call hipCheck(hipMalloc(dx, Nbytes_r))
call hipCheck(hipMalloc(dy, Nbytes_c))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), Nbytes_r, hipMemcpyHostToDevice))
! Forward R2C. Device pointers pass directly to the FFTW C API.
plan = fftw_plan_dft_r2c_1d(N, dx, dy, FFTW_ESTIMATE)
call fftw_execute_dft_r2c(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult_c(1)), dy, Nbytes_c, hipMemcpyDeviceToHost))
max_error = 0.0d0
do j = 0, Nc-1
if (j == 0 .or. j == 1) then
error = abs(hresult_c(j+1) - cmplx(dble(N), 0.0d0, kind=c_double_complex))
else if (j == 3) then
error = abs(hresult_c(j+1) - cmplx(1.5d0 * N, 0.0d0, kind=c_double_complex))
else
error = abs(hresult_c(j+1))
end if
max_error = max(max_error, error)
end do
if (max_error > tol) then
write(*,*) "FAILED! R2C: max error = ", max_error
call exit(1)
end if
! Backward C2R: c2r(r2c(x)) should equal N * x
call hipCheck(hipMemcpy(dy, c_loc(hresult_c(1)), Nbytes_c, hipMemcpyHostToDevice))
call hipCheck(hipMalloc(dz, Nbytes_r))
plan = fftw_plan_dft_c2r_1d(N, dy, dz, FFTW_ESTIMATE)
call fftw_execute_dft_c2r(plan, dy, dz)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult_r(1)), dz, Nbytes_r, hipMemcpyDeviceToHost))
max_error = 0.0d0
do j = 1, N
error = abs(hresult_r(j) - dble(N) * hx(j))
max_error = max(max_error, error)
end do
if (max_error > tol) then
write(*,*) "FAILED! C2R round-trip: max error = ", max_error
call exit(1)
end if
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
call hipCheck(hipFree(dz))
deallocate(hx, hresult_c, hresult_r)
write(*,*) "PASSED!"
end program hipfftw_r2c_c2r_test
Multi-dimensional transforms#
fftw_plan_dft_2d takes the dimensions in C order, so the second argument
varies fastest in memory.
program hipfftw_dft_2d_test
use iso_c_binding
use hipfort
use hipfort_check
use hipfort_hipfftw
implicit none
integer(c_int), parameter :: NX = 4, NY = 8
integer(c_int), parameter :: kx = 1, ky = 3
integer(c_size_t), parameter :: Nbytes = NX * NY * 16 ! sizeof(double complex) = 16
double precision, parameter :: pi = 4.0d0 * atan(1.0d0)
double precision, parameter :: tol = 1.0d-10
! FFTW_ESTIMATE is not emitted by the generated enums module; use the
! standard FFTW planner-flag value.
integer(c_int), parameter :: FFTW_ESTIMATE = 64
complex(c_double_complex), allocatable, target, dimension(:) :: hx, hresult
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer :: ii, jj, kxi, kyi
double precision :: error, max_error
complex(c_double_complex) :: wx, wy, expected
write(*,"(a)",advance="no") "-- Running test 'hipfftw_dft_2d' (Fortran 2003) - "
! Signal: h[ii,jj] = exp(2*pi*i*(kx*ii/NX + ky*jj/NY)), C row-major (jj contiguous).
! Linear index: ii*NY + jj (0-indexed).
! Expected 2D DFT: output bin (kx,ky) = NX*NY, all others zero.
wx = cmplx(0.0d0, 2.0d0 * pi * dble(kx) / dble(NX), kind=c_double_complex)
wy = cmplx(0.0d0, 2.0d0 * pi * dble(ky) / dble(NY), kind=c_double_complex)
allocate(hx(NX*NY), hresult(NX*NY))
do ii = 0, NX-1
do jj = 0, NY-1
hx(ii*NY + jj + 1) = exp(wx*dble(ii)) * exp(wy*dble(jj))
end do
end do
call hipCheck(hipMalloc(dx, Nbytes))
call hipCheck(hipMalloc(dy, Nbytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), Nbytes, hipMemcpyHostToDevice))
! n0=NX (slow), n1=NY (fast); C row-major, no dimension reversal needed.
plan = fftw_plan_dft_2d(NX, NY, dx, dy, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult(1)), dy, Nbytes, hipMemcpyDeviceToHost))
! Verify: output bin (kx,ky) should equal NX*NY; all others zero.
max_error = 0.0d0
do kxi = 0, NX-1
do kyi = 0, NY-1
if (kxi == kx .and. kyi == ky) then
expected = cmplx(dble(NX*NY), 0.0d0, kind=c_double_complex)
else
expected = cmplx(0.0d0, 0.0d0, kind=c_double_complex)
end if
error = abs(hresult(kxi*NY + kyi + 1) - expected)
max_error = max(max_error, error)
end do
end do
if (max_error > tol * dble(NX*NY)) then
write(*,*) "FAILED! max error = ", max_error
call exit(1)
end if
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
deallocate(hx, hresult)
write(*,*) "PASSED!"
end program hipfftw_dft_2d_test
test/f2003/hipfftw/hipfftw_dft_3d.f03 extends the same pattern to three
dimensions with fftw_plan_dft_3d.
Batched transforms#
The _many planners transform a batch of signals with one plan. The
inembed and onembed arguments describe the memory layout, stride
is the gap between elements of one transform and dist the gap between the
start of consecutive transforms. This example covers the complex-to-complex,
real-to-complex and complex-to-real cases.
! =============================================================================
! GPU hipfftw test for the *_many interfaces
! =============================================================================
!
! This program validates hipfort's fftw_plan_many_dft, fftw_plan_many_dft_r2c,
! and fftw_plan_many_dft_c2r wrappers using GPU device memory.
!
! KEY CONVENTION: These are the generated bindings to the FFTW C API, which
! takes dimensions in C order (last index fastest, row-major) and does NOT
! reverse them. For a Fortran array z(NX, NY) with NX fastest, pass the
! reversed dimension list n = [NY, NX] (and likewise for the embed arrays).
! =============================================================================
program hipfftw_many_test
use iso_c_binding
use hipfort
use hipfort_check
use hipfort_hipfftw
implicit none
double precision, parameter :: tol = 1.0d-12
double precision, parameter :: pi = 4.0d0 * atan(1.0d0)
! FFTW_ESTIMATE is not emitted by the generated enums module; use the
! standard FFTW planner-flag value.
integer(c_int), parameter :: FFTW_ESTIMATE = 64
integer :: nfail, seed_size
integer, allocatable :: seed(:)
call random_seed(size=seed_size)
allocate(seed(seed_size))
seed = 42
call random_seed(put=seed)
deallocate(seed)
nfail = 0
write(*,'(a)') "=== hipfftw many GPU Tests ==="
call test_1d_c2c_interleaved(nfail)
call test_2d_c2c_padded_embed(nfail)
call test_1d_c2r_roundtrip(nfail)
call test_many_vs_individual_c2c(nfail)
write(*,*)
if (nfail > 0) then
write(*,'(i0,a)') nfail, " test(s) FAILED"
call exit(1)
else
write(*,*) "PASSED!"
end if
contains
subroutine report(max_err, tolerance, nfail)
double precision, intent(in) :: max_err, tolerance
integer, intent(inout) :: nfail
if (max_err > tolerance) then
write(*,'(a,es12.4,a)') "FAILED (max_err=", max_err, ")"
nfail = nfail + 1
else
write(*,'(a,es12.4,a)') "PASSED (max_err=", max_err, ")"
end if
end subroutine
! ===========================================================================
! 1D C2C, interleaved batches (batch-major layout)
! N=8, howmany=3, istride=howmany=3, idist=1
! Data layout: in(j*3+b+1) = signal(j) for transform b, element j
! Signal: x[j] = exp(2*pi*i*j/N) + 2*exp(2*pi*i*3*j/N)
! Expected DFT: X[1]=N, X[3]=2N, rest 0
! ===========================================================================
subroutine test_1d_c2c_interleaved(nfail)
integer, intent(inout) :: nfail
integer(c_int), parameter :: N = 8, howmany = 3
integer(c_size_t), parameter :: total_bytes = N * howmany * 16
complex(c_double_complex), allocatable, target :: hx(:), hresult(:)
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer(c_int), target :: n_arr(1), ie(1), oe(1)
integer :: j, b
double precision :: max_err, err
complex(c_double_complex) :: w, expected
write(*,'(a)',advance="no") " 1D C2C interleaved batches: "
allocate(hx(N*howmany), hresult(N*howmany))
hx = cmplx(0d0, 0d0, kind=c_double_complex)
w = cmplx(0d0, 2d0*pi/dble(N), kind=c_double_complex)
do b = 0, howmany-1
do j = 0, N-1
hx(j*howmany + b + 1) = exp(w*dble(j)) + 2d0*exp(3d0*w*dble(j))
end do
end do
call hipCheck(hipMalloc(dx, total_bytes))
call hipCheck(hipMalloc(dy, total_bytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), total_bytes, hipMemcpyHostToDevice))
n_arr = [N]; ie = [N]; oe = [N]
plan = fftw_plan_many_dft(1, c_loc(n_arr), howmany, &
dx, c_loc(ie), howmany, 1, dy, c_loc(oe), howmany, 1, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult(1)), dy, total_bytes, hipMemcpyDeviceToHost))
max_err = 0d0
do b = 0, howmany-1
do j = 0, N-1
if (j == 1) then
expected = cmplx(dble(N), 0d0, kind=c_double_complex)
else if (j == 3) then
expected = cmplx(2d0*dble(N), 0d0, kind=c_double_complex)
else
expected = cmplx(0d0, 0d0, kind=c_double_complex)
end if
err = abs(hresult(j*howmany + b + 1) - expected)
max_err = max(max_err, err)
end do
end do
call report(max_err, tol, nfail)
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
deallocate(hx, hresult)
end subroutine
! ===========================================================================
! 2D C2C with padded embed (embed != n)
! NX=4 (fast in Fortran), NY=6 (slow), LDX=8 (padded), howmany=2
! Fortran array: z(LDX, NY, howmany), transform z(1:NX, 1:NY, :)
!
! Generated bindings call the FFTW C API directly (no dimension reversal),
! so pass dimensions in C order: n=[NY,NX], inembed=[NY,LDX].
!
! Signal: z(ix,iy) = exp(2*pi*i*(ix-1)/NX) * exp(2*pi*i*2*(iy-1)/NY)
! Expected 2D DFT (0-indexed): Z(kx=1,ky=2) = NX*NY, rest 0
! ===========================================================================
subroutine test_2d_c2c_padded_embed(nfail)
integer, intent(inout) :: nfail
integer(c_int), parameter :: NX = 4, NY = 6, LDX = 8, howmany = 2
integer(c_size_t), parameter :: total_bytes = LDX * NY * howmany * 16
complex(c_double_complex), allocatable, target :: hx(:), hresult(:)
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer(c_int), target :: n_arr(2), ie(2), oe(2)
integer :: ix, iy, kx, ky, b
double precision :: max_err, err
complex(c_double_complex) :: wx, wy, expected
write(*,'(a)',advance="no") " 2D C2C padded embed: "
allocate(hx(LDX*NY*howmany), hresult(LDX*NY*howmany))
hx = cmplx(0d0, 0d0, kind=c_double_complex)
wx = cmplx(0d0, 2d0*pi/dble(NX), kind=c_double_complex)
wy = cmplx(0d0, 2d0*pi/dble(NY), kind=c_double_complex)
do b = 0, howmany-1
do iy = 0, NY-1
do ix = 0, NX-1
hx(b*LDX*NY + iy*LDX + ix + 1) = exp(wx*dble(ix)) * exp(2d0*wy*dble(iy))
end do
end do
end do
call hipCheck(hipMalloc(dx, total_bytes))
call hipCheck(hipMalloc(dy, total_bytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), total_bytes, hipMemcpyHostToDevice))
! C order (reversed from Fortran): fastest dimension last.
n_arr = [NY, NX]
ie = [NY, LDX]
oe = [NY, LDX]
plan = fftw_plan_many_dft(2, c_loc(n_arr), howmany, &
dx, c_loc(ie), 1, LDX*NY, dy, c_loc(oe), 1, LDX*NY, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult(1)), dy, total_bytes, hipMemcpyDeviceToHost))
max_err = 0d0
do b = 0, howmany-1
do ky = 0, NY-1
do kx = 0, NX-1
if (kx == 1 .and. ky == 2) then
expected = cmplx(dble(NX*NY), 0d0, kind=c_double_complex)
else
expected = cmplx(0d0, 0d0, kind=c_double_complex)
end if
err = abs(hresult(b*LDX*NY + ky*LDX + kx + 1) - expected)
max_err = max(max_err, err)
end do
end do
end do
call report(max_err, tol, nfail)
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
deallocate(hx, hresult)
end subroutine
! ===========================================================================
! 1D C2R round-trip: c2r(r2c(x)) = N * x
! Tests both fftw_plan_many_dft_r2c and fftw_plan_many_dft_c2r.
! Uses random real input (any real signal is valid for R2C).
! ===========================================================================
subroutine test_1d_c2r_roundtrip(nfail)
integer, intent(inout) :: nfail
integer(c_int), parameter :: N = 16, Nc = N/2+1, howmany = 3
integer(c_size_t), parameter :: rbytes = N * howmany * 8
integer(c_size_t), parameter :: cbytes = Nc * howmany * 16
real(c_double), allocatable, target :: hx(:), hresult(:)
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr, dz = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer(c_int), target :: n_arr(1), ie_r(1), oe_c(1), ie_c(1), oe_r(1)
integer :: j, b
double precision :: max_err, err
write(*,'(a)',advance="no") " 1D R2C/C2R round-trip: "
allocate(hx(N*howmany), hresult(N*howmany))
call random_number(hx)
call hipCheck(hipMalloc(dx, rbytes))
call hipCheck(hipMalloc(dy, cbytes))
call hipCheck(hipMalloc(dz, rbytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), rbytes, hipMemcpyHostToDevice))
n_arr = [N]; ie_r = [N]; oe_c = [Nc]; ie_c = [Nc]; oe_r = [N]
plan = fftw_plan_many_dft_r2c(1, c_loc(n_arr), howmany, &
dx, c_loc(ie_r), 1, N, dy, c_loc(oe_c), 1, Nc, FFTW_ESTIMATE)
call fftw_execute_dft_r2c(plan, dx, dy)
call fftw_destroy_plan(plan)
plan = fftw_plan_many_dft_c2r(1, c_loc(n_arr), howmany, &
dy, c_loc(ie_c), 1, Nc, dz, c_loc(oe_r), 1, N, FFTW_ESTIMATE)
call fftw_execute_dft_c2r(plan, dy, dz)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult(1)), dz, rbytes, hipMemcpyDeviceToHost))
max_err = 0d0
do b = 0, howmany-1
do j = 0, N-1
err = abs(hresult(b*N + j + 1) - dble(N) * hx(b*N + j + 1))
max_err = max(max_err, err)
end do
end do
call report(max_err, tol, nfail)
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
call hipCheck(hipFree(dz))
deallocate(hx, hresult)
end subroutine
! ===========================================================================
! 1D C2C: many_dft vs individual dft_1d per batch on GPU
! Uses random complex input (any complex signal is valid for C2C).
! ===========================================================================
subroutine test_many_vs_individual_c2c(nfail)
integer, intent(inout) :: nfail
integer(c_int), parameter :: N = 16, howmany = 5
integer(c_size_t), parameter :: total_bytes = N * howmany * 16
integer(c_size_t), parameter :: slice_bytes = N * 16
complex(c_double_complex), allocatable, target :: hx(:), hout_many(:), hout_ind(:)
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr
type(c_ptr) :: d_sin = c_null_ptr, d_sout = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
integer(c_int), target :: n_arr(1), ie(1), oe(1)
integer :: j
double precision :: max_err, err
real(c_double) :: rr(N*howmany), ri(N*howmany)
integer :: b
write(*,'(a)',advance="no") " 1D C2C many vs individual: "
allocate(hx(N*howmany), hout_many(N*howmany), hout_ind(N*howmany))
call random_number(rr)
call random_number(ri)
do j = 1, N*howmany
hx(j) = cmplx(rr(j), ri(j), kind=c_double_complex)
end do
call hipCheck(hipMalloc(dx, total_bytes))
call hipCheck(hipMalloc(dy, total_bytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), total_bytes, hipMemcpyHostToDevice))
n_arr = [N]; ie = [N]; oe = [N]
plan = fftw_plan_many_dft(1, c_loc(n_arr), howmany, &
dx, c_loc(ie), 1, N, dy, c_loc(oe), 1, N, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hout_many(1)), dy, total_bytes, hipMemcpyDeviceToHost))
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
call hipCheck(hipMalloc(d_sin, slice_bytes))
call hipCheck(hipMalloc(d_sout, slice_bytes))
do b = 0, howmany-1
call hipCheck(hipMemcpy(d_sin, c_loc(hx(b*N+1)), slice_bytes, hipMemcpyHostToDevice))
plan = fftw_plan_dft_1d(N, d_sin, d_sout, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, d_sin, d_sout)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hout_ind(b*N+1)), d_sout, slice_bytes, hipMemcpyDeviceToHost))
end do
call hipCheck(hipFree(d_sin))
call hipCheck(hipFree(d_sout))
max_err = 0d0
do j = 1, N*howmany
err = abs(hout_many(j) - hout_ind(j))
max_err = max(max_err, err)
end do
call report(max_err, tol, nfail)
deallocate(hx, hout_many, hout_ind)
end subroutine
end program hipfftw_many_test
The guru interface#
The guru interface describes a transform as arrays of fftw_iodim
descriptors, one per dimension, each giving a length and its input and output
strides. It expresses layouts the simpler planners cannot.
Note how the arrays are passed: the dummy arguments are scalar
type(fftw_iodim), so the example passes the first element of each array and
the callee receives the base address of the contiguous struct array.
program hipfftw_guru_test
use iso_c_binding
use hipfort
use hipfort_check
use hipfort_hipfftw
use hipfort_hipfftw_types
implicit none
integer(c_int), parameter :: N = 16, HOWMANY = 3
integer(c_size_t), parameter :: Nbytes = N * HOWMANY * 16 ! sizeof(double complex) = 16
double precision, parameter :: pi = 4.0d0 * atan(1.0d0)
double precision, parameter :: tol = 1.0d-10
! FFTW_ESTIMATE is not emitted by the generated enums module; use the
! standard FFTW planner-flag value.
integer(c_int), parameter :: FFTW_ESTIMATE = 64
complex(c_double_complex), allocatable, target, dimension(:) :: hx, hresult
type(c_ptr) :: dx = c_null_ptr, dy = c_null_ptr
type(c_ptr) :: plan = c_null_ptr
type(fftw_iodim), target :: dims(1), howmany_dims(1)
integer :: b, j, k
double precision :: error, max_error
complex(c_double_complex) :: w, expected
write(*,"(a)",advance="no") "-- Running test 'hipfftw_guru' (Fortran 2003) - "
! 1D batched C2C: HOWMANY batches of length N, contiguous (stride=1, dist=N).
! Batch b (0-indexed): x[j] = exp(2*pi*i*(b+1)*j/N).
! Expected forward DFT: output bin b+1 of batch b = N, all others zero.
allocate(hx(N*HOWMANY), hresult(N*HOWMANY))
do b = 0, HOWMANY-1
w = cmplx(0.0d0, 2.0d0 * pi * dble(b+1) / dble(N), kind=c_double_complex)
do j = 0, N-1
hx(b*N + j + 1) = exp(w * dble(j))
end do
end do
call hipCheck(hipMalloc(dx, Nbytes))
call hipCheck(hipMalloc(dy, Nbytes))
call hipCheck(hipMemcpy(dx, c_loc(hx(1)), Nbytes, hipMemcpyHostToDevice))
dims(1) = fftw_iodim(N, 1, 1) ! n=N, is=1, os=1
howmany_dims(1) = fftw_iodim(HOWMANY, N, N) ! n=HOWMANY, is=N, os=N
! The dims/howmany_dims dummies are declared scalar type(fftw_iodim); pass the
! first element of each array so the callee receives the base address of
! the contiguous struct array — looks like an element but acts as a pointer.
plan = fftw_plan_guru_dft(1, dims(1), 1, howmany_dims(1), &
dx, dy, FFTW_FORWARD, FFTW_ESTIMATE)
call fftw_execute_dft(plan, dx, dy)
call fftw_destroy_plan(plan)
call hipCheck(hipMemcpy(c_loc(hresult(1)), dy, Nbytes, hipMemcpyDeviceToHost))
! Verify: for batch b, output bin b+1 should equal N; all others zero.
max_error = 0.0d0
do b = 0, HOWMANY-1
do k = 0, N-1
if (k == b+1) then
expected = cmplx(dble(N), 0.0d0, kind=c_double_complex)
else
expected = cmplx(0.0d0, 0.0d0, kind=c_double_complex)
end if
error = abs(hresult(b*N + k + 1) - expected)
max_error = max(max_error, error)
end do
end do
if (max_error > tol * dble(N)) then
write(*,*) "FAILED! max error = ", max_error
call exit(1)
end if
call hipCheck(hipFree(dx))
call hipCheck(hipFree(dy))
deallocate(hx, hresult)
write(*,*) "PASSED!"
end program hipfftw_guru_test
Allocating buffers#
fftw_alloc_real and fftw_alloc_complex, along with the fftwf_
single precision forms, return correctly aligned host-accessible buffers that
can be handed straight to a plan. Use c_f_pointer to get a Fortran array
view, and release them with fftw_free.
! hipFFTW allocation API: fftw_alloc_real / fftw_alloc_complex and their
! single-precision counterparts return host-accessible buffers that can be
! handed straight to a plan, mirroring the allocation tests in
! clients/tests/hipfftw_test.cpp.
program hipfftw_alloc_test
use iso_c_binding
use hipfort
use hipfort_check
use hipfort_hipfftw
implicit none
integer(c_int), parameter :: N = 16
integer(c_int), parameter :: Nc = N / 2 + 1
double precision, parameter :: pi = 4.0d0 * atan(1.0d0)
double precision, parameter :: tol = 1.0d-12
real, parameter :: tol_s = 1.0e-4
! FFTW_ESTIMATE is not emitted by the generated enums module; use the
! standard FFTW planner-flag value.
integer(c_int), parameter :: FFTW_ESTIMATE = 64
type(c_ptr) :: pr = c_null_ptr, pc = c_null_ptr, plan = c_null_ptr
real(c_double), pointer :: x(:) => null()
complex(c_double_complex), pointer :: y(:) => null()
real(c_float), pointer :: xs(:) => null()
complex(c_float_complex), pointer :: ys(:) => null()
integer :: j
double precision :: error, max_error
write(*,"(a)",advance="no") "-- Running test 'hipfftw_alloc' (Fortran 2003) - "
! Real signal: x[j] = 1 + 2*cos(2*pi*j/N) + 3*cos(2*pi*3*j/N)
! R2C output (0-indexed): X[0] = N, X[1] = N, X[3] = 3*N/2, rest zero.
pr = fftw_alloc_real(int(N, c_size_t))
pc = fftw_alloc_complex(int(Nc, c_size_t))
if (.not. c_associated(pr) .or. .not. c_associated(pc)) then
write(*,*) "FAILED! fftw_alloc_real/fftw_alloc_complex returned a null pointer"
call exit(1)
end if
call c_f_pointer(pr, x, [N])
call c_f_pointer(pc, y, [Nc])
do j = 0, N-1
x(j+1) = 1.0d0 + 2.0d0 * cos(2.0d0 * pi * j / dble(N)) &
+ 3.0d0 * cos(6.0d0 * pi * j / dble(N))
end do
plan = fftw_plan_dft_r2c_1d(N, pr, pc, FFTW_ESTIMATE)
call fftw_execute_dft_r2c(plan, pr, pc)
call fftw_destroy_plan(plan)
max_error = 0.0d0
do j = 0, Nc-1
if (j == 0 .or. j == 1) then
error = abs(y(j+1) - cmplx(dble(N), 0.0d0, kind=c_double_complex))
else if (j == 3) then
error = abs(y(j+1) - cmplx(1.5d0 * N, 0.0d0, kind=c_double_complex))
else
error = abs(y(j+1))
end if
max_error = max(max_error, error)
end do
call fftw_free(pr)
call fftw_free(pc)
if (max_error > tol) then
write(*,*) "FAILED! double precision: max error = ", max_error
call exit(1)
end if
pr = fftwf_alloc_real(int(N, c_size_t))
pc = fftwf_alloc_complex(int(Nc, c_size_t))
if (.not. c_associated(pr) .or. .not. c_associated(pc)) then
write(*,*) "FAILED! fftwf_alloc_real/fftwf_alloc_complex returned a null pointer"
call exit(1)
end if
call c_f_pointer(pr, xs, [N])
call c_f_pointer(pc, ys, [Nc])
do j = 0, N-1
xs(j+1) = real(1.0d0 + 2.0d0 * cos(2.0d0 * pi * j / dble(N)) &
+ 3.0d0 * cos(6.0d0 * pi * j / dble(N)), c_float)
end do
plan = fftwf_plan_dft_r2c_1d(N, pr, pc, FFTW_ESTIMATE)
call fftwf_execute_dft_r2c(plan, pr, pc)
call fftwf_destroy_plan(plan)
max_error = 0.0d0
do j = 0, Nc-1
if (j == 0 .or. j == 1) then
error = abs(ys(j+1) - cmplx(real(N), 0.0, kind=c_float_complex))
else if (j == 3) then
error = abs(ys(j+1) - cmplx(1.5 * N, 0.0, kind=c_float_complex))
else
error = abs(ys(j+1))
end if
max_error = max(max_error, error)
end do
call fftwf_free(pr)
call fftwf_free(pc)
if (max_error > tol_s) then
write(*,*) "FAILED! single precision: max error = ", max_error
call exit(1)
end if
write(*,*) "PASSED!"
end program hipfftw_alloc_test