// Copyright (c) 2018 - 2022 Advanced Micro Devices, Inc. All rights // reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "hipfft/hipfft.h" #include #include #include #include #include #include "../../shared/fftw_transform.h" #include "../../shared/params_gen.h" #include "../hipfft_params.h" DISABLE_WARNING_PUSH DISABLE_WARNING_DEPRECATED_DECLARATIONS DISABLE_WARNING_RETURN_TYPE #include DISABLE_WARNING_POP // Function to return maximum error for float and double types for // the simple tests below template inline double type_epsilon_simple(); template <> inline double type_epsilon_simple() { return 1e-6; } template <> inline double type_epsilon_simple() { return 1e-7; } /* Static utility class template helping with the definition of valid/invalid values for arguments of (un)scoped enumeration types in API testing. Usage: for an enumeration type of interest, say "enum_of_interest", define template <> const std::vector enum_helper::valid_values = {all, the, known, valid, values, of, type, enum_of_interest}; before using any of this class' self-explanatory public member functions. */ template , bool> = true> class enum_helper { using base_t = typename std::underlying_type::type; // static class cannot be instantiated, copied, or moved enum_helper() = delete; ~enum_helper() = delete; enum_helper(const enum_helper&) = delete; enum_helper(enum_helper&& other) = delete; enum_helper& operator=(const enum_helper&) = delete; enum_helper& operator=(enum_helper&& other) = delete; public: static const std::vector valid_values; static bool has_value(const T& val) { return std::any_of( valid_values.begin(), valid_values.end(), [&](const T& v) { return v == val; }); } static size_t num_valid_values() { return valid_values.size(); } static T get_any_valid_value(size_t prng_seed = 0) { const size_t nvals = num_valid_values(); if(nvals == 0) throw std::runtime_error( "enum_helper::get_any_valid_value: no valid value is defined."); std::ranlux24_base gen(prng_seed); return valid_values[static_cast(gen()) % nvals]; } static T get_invalid_value(size_t prng_seed = 0) { constexpr base_t max_base_val = std::numeric_limits::max(); constexpr base_t min_base_val = std::numeric_limits::min(); std::ranlux24_base gen(prng_seed); std::uniform_int_distribution dis(min_base_val, max_base_val); // limit number of attempts to 10x the number of possible value size_t num_attempts = 0; T made_up_value; auto generate_candidate = [&]() { num_attempts++; made_up_value = static_cast(dis(gen)); return; }; generate_candidate(); while(has_value(made_up_value) && num_attempts < 10 * num_valid_values()) { generate_candidate(); } if(has_value(made_up_value)) throw std::runtime_error( "enum_helper::get_invalid_value failed to generate an invalid valid"); return made_up_value; } }; // definition of valid values for various enum types template <> const std::vector enum_helper::valid_values = {hipfftLibraryPropertyType::HIPFFT_MAJOR_VERSION, hipfftLibraryPropertyType::HIPFFT_MINOR_VERSION, hipfftLibraryPropertyType::HIPFFT_PATCH_LEVEL}; TEST(hipfftTest, Create1dPlan) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; size_t length = 1024; ASSERT_EQ(hipfftPlan1d(&plan, length, HIPFFT_C2C, 1), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, CreatePlanMany) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } int const rank = 3; int const nX = 64; int const nY = 128; int const nZ = 23; int n[3] = {nX, nY, nZ}; int inembed[3] = {nX, nY, nZ}; int* inembed_null = nullptr; int const istride = 1; int const idist = nX * nY * nZ; int onembed[3] = {nX, nY, nZ}; int* onembed_null = nullptr; int const ostride = 1; int const odist = nX * nY * nZ; hipfftType type = HIPFFT_C2C; int const batch = 1000; size_t workSize; // Tests plan creation with null and not null // combinations of inembed and onembed. // // Valid combinations: // inembed == null && onembed == null // or // inembed != null && onembed != null // // otherwise HIPFFT_INVALID_VALUE should be // returned to maintain compatibility with cuFFT // inembed == null && onembed == null { hipfftHandle plan_valid_1 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_valid_1), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany(plan_valid_1, rank, (int*)n, inembed_null, istride, idist, onembed_null, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS) << "inembed == null && onembed == null failed: " << hipfftResult_string(ret_hipfft); ASSERT_EQ(hipfftSetAutoAllocation(plan_valid_1, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan_valid_1), HIPFFT_SUCCESS); } // inembed != null && onembed != null { hipfftHandle plan_valid_2 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_valid_2), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany(plan_valid_2, rank, (int*)n, inembed, istride, idist, onembed, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS) << "inembed != null && onembed != null failed: " << hipfftResult_string(ret_hipfft); ASSERT_EQ(hipfftSetAutoAllocation(plan_valid_2, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan_valid_2), HIPFFT_SUCCESS); } // inembed != null && onembed == null { hipfftHandle plan_invalid_1 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_invalid_1), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany(plan_invalid_1, rank, (int*)n, inembed, istride, idist, onembed_null, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_VALUE) << "inembed != null && onembed == null failed: " << hipfftResult_string(ret_hipfft); ASSERT_EQ(hipfftDestroy(plan_invalid_1), HIPFFT_SUCCESS); } // inembed == null && onembed != null { hipfftHandle plan_invalid_2 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_invalid_2), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany(plan_invalid_2, rank, (int*)n, inembed_null, istride, idist, onembed, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_VALUE) << "inembed == null && onembed != null failed: " << hipfftResult_string(ret_hipfft); ASSERT_EQ(hipfftDestroy(plan_invalid_2), HIPFFT_SUCCESS); } } TEST(hipfftTest, CreatePlanMany64) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } int const rank = 3; long long int const nX = 64; long long int const nY = 128; long long int const nZ = 23; long long int n[3] = {nX, nY, nZ}; long long int inembed[3] = {nX, nY, nZ}; long long int const istride = 1; long long int const idist = nX * nY * nZ; long long int onembed[3] = {nX, nY, nZ}; long long int onembed_invalid[3] = {nX, nY, -nZ}; long long int const ostride = 1; long long int const odist = nX * nY * nZ; hipfftType type = HIPFFT_C2C; long long int const batch = 1000; long long int const batch_invalid = -2; size_t workSize; // Tests the 64-bit version of plan creation // with valid/invalid data layouts. // First test with a valid data layout { hipfftHandle plan_valid = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_valid), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany64(plan_valid, rank, (long long int*)n, inembed, istride, idist, onembed, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS); ASSERT_EQ(hipfftSetAutoAllocation(plan_valid, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan_valid), HIPFFT_SUCCESS); } // invalid data layout (n array has a negative entry). only test rocFFT // backend, since it's more strict #ifdef __HIP_PLATFORM_AMD__ long long int n_invalid[3] = {nX, -nY, nZ}; { hipfftHandle plan_invalid_1 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_invalid_1), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany64(plan_invalid_1, rank, (long long int*)n_invalid, inembed, istride, idist, onembed, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_VALUE); ASSERT_EQ(hipfftSetAutoAllocation(plan_invalid_1, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan_invalid_1), HIPFFT_SUCCESS); } #endif // invalid data layout (onembed array has a negative entry) { hipfftHandle plan_invalid_2 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_invalid_2), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany64(plan_invalid_2, rank, (long long int*)n, inembed, istride, idist, onembed_invalid, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_SIZE); ASSERT_EQ(hipfftSetAutoAllocation(plan_invalid_2, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan_invalid_2), HIPFFT_SUCCESS); } // invalid data layout (batch is negative) { hipfftHandle plan_invalid_3 = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan_invalid_3), HIPFFT_SUCCESS); auto ret_hipfft = hipfftMakePlanMany64(plan_invalid_3, rank, (long long int*)n, inembed, istride, idist, onembed, ostride, odist, type, batch_invalid, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_SIZE); ASSERT_EQ(hipfftSetAutoAllocation(plan_invalid_3, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan_invalid_3), HIPFFT_SUCCESS); } } TEST(hipfftTest, hipfftGetSizeMany) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } int const rank = 3; int const nX = 33; int const nY = 128; int const nZ = 100; int n[3] = {nX, nY, nZ}; int inembed[3] = {nX, nY, nZ}; int const istride = 1; int const idist = nX * nY * nZ; int onembed[3] = {nX, nY, nZ}; int const ostride = 1; int const odist = nX * nY * nZ; hipfftType type = HIPFFT_C2C; int const batch = 1; size_t workSize; hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); auto ret_hipfft = hipfftGetSizeMany(plan, rank, (int*)n, inembed, istride, idist, onembed, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS); ASSERT_EQ(hipfftSetAutoAllocation(plan, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, hipfftGetSizeMany64) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } int const rank = 3; long long int const nX = 133; long long int const nY = 354; long long int const nZ = 256; long long int n[3] = {nX, nY, nZ}; long long int inembed[3] = {nX, nY, nZ}; long long int const istride = 1; long long int const idist = nX * nY * nZ; long long int onembed[3] = {nX, nY, nZ}; long long int const ostride = 1; long long int const odist = nX * nY * nZ; hipfftType type = HIPFFT_C2C; long long int const batch = 2; size_t workSize; hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); auto ret_hipfft = hipfftGetSizeMany64(plan, rank, (long long int*)n, inembed, istride, idist, onembed, ostride, odist, type, batch, &workSize); ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS); ASSERT_EQ(hipfftSetAutoAllocation(plan, 0), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, CheckBufferSizeC2C) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); size_t n = 1024; size_t workSize = 0; ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_C2C, 1, &workSize), HIPFFT_SUCCESS); #ifdef __HIP_PLATFORM_AMD__ // No extra work buffer for C2C EXPECT_EQ(workSize, 0); #endif ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, CheckBufferSizeR2C) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); // real forward transform cannot modify input, so we need to pick // a sufficiently small N such that rocFFT can fuse // post-processing into one kernel and avoid a temp buffer size_t n = 256; size_t workSize = 0; ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_R2C, 1, &workSize), HIPFFT_SUCCESS); #ifdef __HIP_PLATFORM_AMD__ // NOTE: keep this condition for ease of changing n for ad-hoc tests // // cppcheck-suppress knownConditionTrueFalse if(n % 2 == 0) { EXPECT_EQ(workSize, 0); } else { EXPECT_EQ(workSize, 2 * n * sizeof(float)); } #endif EXPECT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, CheckBufferSizeC2R) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); size_t n = 2048; size_t workSize = 0; ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_C2R, 1, &workSize), HIPFFT_SUCCESS); #ifdef __HIP_PLATFORM_AMD__ // NOTE: keep this condition for ease of changing n for ad-hoc tests // // cppcheck-suppress knownConditionTrueFalse if(n % 2 == 0) { EXPECT_EQ(workSize, 0); } else { EXPECT_EQ(workSize, 2 * n * sizeof(float)); } #endif ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, CheckBufferSizeD2Z) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); // real forward transform cannot modify input, so we need to pick // a sufficiently small N such that rocFFT can fuse // post-processing into one kernel and avoid a temp buffer size_t n = 256; size_t batch = 1000; size_t workSize = 0; ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_D2Z, batch, &workSize), HIPFFT_SUCCESS); #ifdef __HIP_PLATFORM_AMD__ // NOTE: keep this condition for ease of changing n for ad-hoc tests // // cppcheck-suppress knownConditionTrueFalse if(n % 2 == 0) { EXPECT_EQ(workSize, 0); } else { EXPECT_EQ(workSize, 2 * n * sizeof(double)); } #endif ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } TEST(hipfftTest, CheckBufferSizeZ2D) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); size_t n = 2048; size_t batch = 1000; size_t workSize = 0; ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_Z2D, batch, &workSize), HIPFFT_SUCCESS); #ifdef __HIP_PLATFORM_AMD__ // NOTE: keep this condition for ease of changing n for ad-hoc tests // // cppcheck-suppress knownConditionTrueFalse if(n % 2 == 0) { EXPECT_EQ(workSize, 0); } else { EXPECT_EQ(workSize, 2 * n * sizeof(double)); } #endif ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } #ifdef __HIP_PLATFORM_AMD__ TEST(hipfftTest, CheckNullWorkBuffer) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); size_t n = 2048; size_t batch = 1000; size_t workSize = 0; ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_Z2D, batch, &workSize), HIPFFT_SUCCESS); EXPECT_EQ(hipfftSetWorkArea(plan, nullptr), HIPFFT_SUCCESS); ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); } #endif TEST(hipfftTest, RunR2C) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } const size_t N = 4096; float in[N]; for(size_t i = 0; i < N; i++) in[i] = i + (i % 3) - (i % 7); hipfftReal* d_in; hipfftComplex* d_out; ASSERT_EQ(hipMalloc(&d_in, N * sizeof(hipfftReal)), hipSuccess); ASSERT_EQ(hipMalloc(&d_out, (N / 2 + 1) * sizeof(hipfftComplex)), hipSuccess); ASSERT_EQ(hipMemcpy(d_in, in, N * sizeof(hipfftReal), hipMemcpyHostToDevice), hipSuccess); hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); size_t workSize; ASSERT_EQ(hipfftMakePlan1d(plan, N, HIPFFT_R2C, 1, &workSize), HIPFFT_SUCCESS); EXPECT_EQ(hipfftExecR2C(plan, d_in, d_out), HIPFFT_SUCCESS); std::vector out(N / 2 + 1); ASSERT_EQ(hipMemcpy(&out[0], d_out, (N / 2 + 1) * sizeof(hipfftComplex), hipMemcpyDeviceToHost), hipSuccess); ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); ASSERT_EQ(hipFree(d_in), hipSuccess); ASSERT_EQ(hipFree(d_out), hipSuccess); ; // NOTE: keep this condition for ease of changing n for ad-hoc tests // // cppcheck-suppress knownConditionTrueFalse if(N % 2 != 0) { EXPECT_TRUE(workSize != 0); } double ref_in[N]; for(size_t i = 0; i < N; i++) ref_in[i] = in[i]; fftw_complex* ref_out; ref_out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * (N / 2 + 1)); fftw_plan_wrapper_t ref_p = fftw_trait::make_wrapper(fftw_plan_dft_r2c_1d(N, ref_in, ref_out, FFTW_ESTIMATE)); fftw_execute_type(ref_p); double maxv = 0; double nrmse = 0; // normalized root mean square error for(size_t i = 0; i < (N / 2 + 1); i++) { // printf("element %d: FFTW result %f, %f; hipFFT result %f, %f \n", (int)i, ref_out[i][0], ref_out[i][1], out[i].x, out[i].y); double dr = ref_out[i][0] - out[i].x; double di = ref_out[i][1] - out[i].y; maxv = fabs(ref_out[i][0]) > maxv ? fabs(ref_out[i][0]) : maxv; maxv = fabs(ref_out[i][1]) > maxv ? fabs(ref_out[i][1]) : maxv; nrmse += ((dr * dr) + (di * di)); } nrmse /= (double)((N / 2 + 1)); nrmse = sqrt(nrmse); nrmse /= maxv; fftw_free(ref_out); EXPECT_LT(nrmse, type_epsilon_simple()); } // ask for a transform whose parameters are only valid out-of-place. // since hipFFT generates both in-place and out-place plans up front // (because it's not told about the placement until exec time), this // ensures that a failure to create an in-place plan doesn't prevent // the out-place plan from working. TEST(hipfftTest, OutplaceOnly) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } static const int N_in_const = 4; static const int N_out_const = N_in_const / 2 + 1; // mutable sizes for passing to hipFFT int N_in = N_in_const; int N_out = N_out_const; float in[N_in_const]; for(int i = 0; i < N_in; i++) in[i] = i + (i % 3) - (i % 7); hipfftReal* d_in; hipfftComplex* d_out; ASSERT_EQ(hipMalloc(&d_in, N_in * sizeof(hipfftReal)), hipSuccess); ASSERT_EQ(hipMalloc(&d_out, N_out * sizeof(hipfftComplex)), hipSuccess); ASSERT_EQ(hipMemcpy(d_in, in, N_in * sizeof(hipfftReal), hipMemcpyHostToDevice), hipSuccess); hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE; ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS); ASSERT_EQ(hipfftPlanMany(&plan, 1, &N_in, &N_in, 1, N_in, &N_out, 1, N_out, HIPFFT_R2C, 1), HIPFFT_SUCCESS); ASSERT_EQ(plan == hipfft_params::INVALID_PLAN_HANDLE, false); ASSERT_EQ(hipfftExecR2C(plan, d_in, d_out), HIPFFT_SUCCESS) << "hipfftExecR2C failed"; std::vector out(N_out); ASSERT_EQ(hipMemcpy(out.data(), d_out, N_out * sizeof(hipfftComplex), hipMemcpyDeviceToHost), hipSuccess); // in-place transform isn't really *supposed* to work - this // might or might not fail but we can at least check that it // doesn't blow up. //hipfftExecR2C(plan, reinterpret_cast(d_out), d_out); ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS); ASSERT_EQ(hipFree(d_in), hipSuccess); ASSERT_EQ(hipFree(d_out), hipSuccess); double ref_in[N_in_const]; for(int i = 0; i < N_in_const; i++) ref_in[i] = in[i]; fftw_complex* ref_out; ref_out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * N_out); fftw_plan_wrapper_t ref_p = fftw_trait::make_wrapper( fftw_plan_dft_r2c_1d(N_in, ref_in, ref_out, FFTW_ESTIMATE)); fftw_execute_type(ref_p); double maxv = 0; double nrmse = 0; // normalized root mean square error for(int i = 0; i < N_out; i++) { // printf("element %d: FFTW result %f, %f; hipFFT result %f, %f \n", (int)i, ref_out[i][0], ref_out[i][1], out[i].x, out[i].y); double dr = ref_out[i][0] - out[i].x; double di = ref_out[i][1] - out[i].y; maxv = fabs(ref_out[i][0]) > maxv ? fabs(ref_out[i][0]) : maxv; maxv = fabs(ref_out[i][1]) > maxv ? fabs(ref_out[i][1]) : maxv; nrmse += ((dr * dr) + (di * di)); } nrmse /= (double)(N_out); nrmse = sqrt(nrmse); nrmse /= maxv; fftw_free(ref_out); ASSERT_LT(nrmse, type_epsilon_simple()); } static constexpr int absurd_version_or_property = std::numeric_limits::min(); TEST(hipfftTest, GetVersion) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } // valid use case(s) int tmp = absurd_version_or_property; EXPECT_EQ(hipfftGetVersion(&tmp), HIPFFT_SUCCESS); EXPECT_NE(tmp, absurd_version_or_property); EXPECT_EQ(hipfftGetVersion(nullptr), HIPFFT_INVALID_VALUE); } TEST(hipfftTest, GetProperty) { if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name()) > unittest_prob) { GTEST_SKIP(); } // valid use case(s) int tmp; for(auto prop_type : enum_helper::valid_values) { tmp = absurd_version_or_property; EXPECT_EQ(hipfftGetProperty(prop_type, &tmp), HIPFFT_SUCCESS); EXPECT_NE(tmp, absurd_version_or_property); } // invalid use case(s) const auto valid_property_type = enum_helper::get_any_valid_value(); EXPECT_EQ(hipfftGetProperty(valid_property_type, nullptr), HIPFFT_INVALID_VALUE); const auto invalid_property_type = enum_helper::get_invalid_value(); EXPECT_EQ(hipfftGetProperty(invalid_property_type, &tmp), HIPFFT_INVALID_VALUE); }