197 lines
5.9 KiB
C++
197 lines
5.9 KiB
C++
// Copyright 2019 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#include "testing/gtest/include/gtest/gtest-death-test.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "third_party/pffft/src/fftpack.h"
|
|
#include "third_party/pffft/src/pffft.h"
|
|
|
|
namespace pffft {
|
|
namespace test {
|
|
namespace {
|
|
|
|
static constexpr int kFftSizes[] = {
|
|
16, 32, 64, 96, 128, 160, 192, 256, 288, 384, 5 * 96, 512,
|
|
576, 5 * 128, 800, 864, 1024, 2048, 2592, 4000, 4096, 12000, 36864};
|
|
|
|
double frand() {
|
|
return rand() / (double)RAND_MAX;
|
|
}
|
|
|
|
void PffftValidate(int fft_size, bool complex_fft) {
|
|
PFFFT_Setup* pffft_status =
|
|
pffft_new_setup(fft_size, complex_fft ? PFFFT_COMPLEX : PFFFT_REAL);
|
|
ASSERT_TRUE(pffft_status) << "FFT size (" << fft_size << ") not supported.";
|
|
|
|
int num_floats = fft_size * (complex_fft ? 2 : 1);
|
|
int num_bytes = num_floats * sizeof(float);
|
|
float* ref = static_cast<float*>(pffft_aligned_malloc(num_bytes));
|
|
float* in = static_cast<float*>(pffft_aligned_malloc(num_bytes));
|
|
float* out = static_cast<float*>(pffft_aligned_malloc(num_bytes));
|
|
float* tmp = static_cast<float*>(pffft_aligned_malloc(num_bytes));
|
|
float* tmp2 = static_cast<float*>(pffft_aligned_malloc(num_bytes));
|
|
|
|
for (int pass = 0; pass < 2; ++pass) {
|
|
SCOPED_TRACE(pass);
|
|
float ref_max = 0;
|
|
int k;
|
|
|
|
// Compute reference solution with FFTPACK.
|
|
if (pass == 0) {
|
|
float* fftpack_buff =
|
|
static_cast<float*>(malloc(2 * num_bytes + 15 * sizeof(float)));
|
|
for (k = 0; k < num_floats; ++k) {
|
|
ref[k] = in[k] = frand() * 2 - 1;
|
|
out[k] = 1e30;
|
|
}
|
|
|
|
if (!complex_fft) {
|
|
rffti(fft_size, fftpack_buff);
|
|
rfftf(fft_size, ref, fftpack_buff);
|
|
// Use our ordering for real FFTs instead of the one of fftpack.
|
|
{
|
|
float refN = ref[fft_size - 1];
|
|
for (k = fft_size - 2; k >= 1; --k)
|
|
ref[k + 1] = ref[k];
|
|
ref[1] = refN;
|
|
}
|
|
} else {
|
|
cffti(fft_size, fftpack_buff);
|
|
cfftf(fft_size, ref, fftpack_buff);
|
|
}
|
|
|
|
free(fftpack_buff);
|
|
}
|
|
|
|
for (k = 0; k < num_floats; ++k) {
|
|
ref_max = std::max(ref_max, fabs(ref[k]));
|
|
}
|
|
|
|
// Pass 0: non canonical ordering of transform coefficients.
|
|
if (pass == 0) {
|
|
// Test forward transform, with different input / output.
|
|
pffft_transform(pffft_status, in, tmp, nullptr, PFFFT_FORWARD);
|
|
memcpy(tmp2, tmp, num_bytes);
|
|
memcpy(tmp, in, num_bytes);
|
|
pffft_transform(pffft_status, tmp, tmp, nullptr, PFFFT_FORWARD);
|
|
for (k = 0; k < num_floats; ++k) {
|
|
SCOPED_TRACE(k);
|
|
EXPECT_EQ(tmp2[k], tmp[k]);
|
|
}
|
|
|
|
// Test reordering.
|
|
pffft_zreorder(pffft_status, tmp, out, PFFFT_FORWARD);
|
|
pffft_zreorder(pffft_status, out, tmp, PFFFT_BACKWARD);
|
|
for (k = 0; k < num_floats; ++k) {
|
|
SCOPED_TRACE(k);
|
|
EXPECT_EQ(tmp2[k], tmp[k]);
|
|
}
|
|
pffft_zreorder(pffft_status, tmp, out, PFFFT_FORWARD);
|
|
} else {
|
|
// Pass 1: canonical ordering of transform coefficients.
|
|
pffft_transform_ordered(pffft_status, in, tmp, nullptr, PFFFT_FORWARD);
|
|
memcpy(tmp2, tmp, num_bytes);
|
|
memcpy(tmp, in, num_bytes);
|
|
pffft_transform_ordered(pffft_status, tmp, tmp, nullptr, PFFFT_FORWARD);
|
|
for (k = 0; k < num_floats; ++k) {
|
|
SCOPED_TRACE(k);
|
|
EXPECT_EQ(tmp2[k], tmp[k]);
|
|
}
|
|
memcpy(out, tmp, num_bytes);
|
|
}
|
|
|
|
{
|
|
for (k = 0; k < num_floats; ++k) {
|
|
SCOPED_TRACE(k);
|
|
EXPECT_NEAR(ref[k], out[k], 1e-3 * ref_max) << "Forward FFT mismatch";
|
|
}
|
|
|
|
if (pass == 0) {
|
|
pffft_transform(pffft_status, tmp, out, nullptr, PFFFT_BACKWARD);
|
|
} else {
|
|
pffft_transform_ordered(pffft_status, tmp, out, nullptr,
|
|
PFFFT_BACKWARD);
|
|
}
|
|
memcpy(tmp2, out, num_bytes);
|
|
memcpy(out, tmp, num_bytes);
|
|
if (pass == 0) {
|
|
pffft_transform(pffft_status, out, out, nullptr, PFFFT_BACKWARD);
|
|
} else {
|
|
pffft_transform_ordered(pffft_status, out, out, nullptr,
|
|
PFFFT_BACKWARD);
|
|
}
|
|
for (k = 0; k < num_floats; ++k) {
|
|
assert(tmp2[k] == out[k]);
|
|
out[k] *= 1.f / fft_size;
|
|
}
|
|
for (k = 0; k < num_floats; ++k) {
|
|
SCOPED_TRACE(k);
|
|
EXPECT_NEAR(in[k], out[k], 1e-3 * ref_max) << "Reverse FFT mismatch";
|
|
}
|
|
}
|
|
|
|
// Quick test of the circular convolution in FFT domain.
|
|
{
|
|
float conv_err = 0, conv_max = 0;
|
|
|
|
pffft_zreorder(pffft_status, ref, tmp, PFFFT_FORWARD);
|
|
memset(out, 0, num_bytes);
|
|
pffft_zconvolve_accumulate(pffft_status, ref, ref, out, 1.0);
|
|
pffft_zreorder(pffft_status, out, tmp2, PFFFT_FORWARD);
|
|
|
|
for (k = 0; k < num_floats; k += 2) {
|
|
float ar = tmp[k], ai = tmp[k + 1];
|
|
if (complex_fft || k > 0) {
|
|
tmp[k] = ar * ar - ai * ai;
|
|
tmp[k + 1] = 2 * ar * ai;
|
|
} else {
|
|
tmp[0] = ar * ar;
|
|
tmp[1] = ai * ai;
|
|
}
|
|
}
|
|
|
|
for (k = 0; k < num_floats; ++k) {
|
|
float d = fabs(tmp[k] - tmp2[k]), e = fabs(tmp[k]);
|
|
if (d > conv_err)
|
|
conv_err = d;
|
|
if (e > conv_max)
|
|
conv_max = e;
|
|
}
|
|
EXPECT_LT(conv_err, 1e-5 * conv_max) << "zconvolve error";
|
|
}
|
|
}
|
|
|
|
pffft_destroy_setup(pffft_status);
|
|
pffft_aligned_free(ref);
|
|
pffft_aligned_free(in);
|
|
pffft_aligned_free(out);
|
|
pffft_aligned_free(tmp);
|
|
pffft_aligned_free(tmp2);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(PffftTest, ValidateReal) {
|
|
for (int fft_size : kFftSizes) {
|
|
SCOPED_TRACE(fft_size);
|
|
if (fft_size == 16) {
|
|
continue;
|
|
}
|
|
PffftValidate(fft_size, /*complex_fft=*/false);
|
|
}
|
|
}
|
|
|
|
TEST(PffftTest, ValidateComplex) {
|
|
for (int fft_size : kFftSizes) {
|
|
SCOPED_TRACE(fft_size);
|
|
PffftValidate(fft_size, /*complex_fft=*/true);
|
|
}
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace pffft
|