JayBeams  0.1
Another project to have fun coding.
ut_plan_many.cpp
Go to the documentation of this file.
1 #include <jb/fftw/plan.hpp>
3 #include <valgrind/valgrind.h>
4 
5 #include <boost/multi_array.hpp>
6 #include <boost/test/unit_test.hpp>
7 #include <algorithm>
8 
9 /**
10  * Helper functions to test jb::fftw::plan in the many timeseries cases
11  */
12 namespace {
13 
14 /// Verify that plans work for a batch of complex vectors into a batch
15 /// of complex vectors
16 template <typename precision_t>
17 void test_plan_complex2complex() {
18  // Define some constants used to size the test, the size does not
19  // matter as much as the fact that we are testing multiple
20  // dimensions.
21  int const F = 2;
22  int const S = 32;
23  int const nsamples = 1 << 15;
24  // The tolerance (define as a fraction of 1.0) depends on the size
25  // of the test because the error in a FFT depends on the size of the
26  // vector:
27  // https://en.wikipedia.org/wiki/Fast_Fourier_transform#Accuracy
28  // we use a very rough approximation to the error here ...
29  int const tol = nsamples;
30 
31  // ... this case is all about testing the FFT transforms between
32  // multiple vectors of std::complex<> values ...
33  typedef std::complex<precision_t> complex;
34 
35  // ... we define an input array, an intermediate array to contain
36  // the FFT, and an output array that should (if things work Okay)
37  // contain the same contents as the input after a FFT and inverse
38  // FFT transformation ...
39  boost::multi_array<complex, 3> in(boost::extents[F][S][nsamples]);
40  boost::multi_array<complex, 3> tmp(boost::extents[F][S][nsamples]);
41  boost::multi_array<complex, 3> out(boost::extents[F][S][nsamples]);
42 
43  // ... initialize the input with a series of pretty straight forward
44  // triangular functions ...
45  for (auto subarray : in) {
46  for (auto vector : subarray) {
47  std::size_t h = nsamples / 2;
48  for (std::size_t i = 0; i != h; ++i) {
49  vector[i] = complex(i - h / 4.0, 0);
50  vector[i + h] = complex(h / 4.0 - i, 0);
51  }
52  }
53  }
54 
55  // ... create a plan to apply the DFT (direct, not inverse) from the
56  // in array to the tmp array ...
57  auto dir = jb::fftw::create_forward_plan(in, tmp);
58  // ... create a plan to apply the inverse DFT from the tmp array to
59  // the out array ...
60  auto inv = jb::fftw::create_backward_plan(tmp, out);
61 
62  // ... execute the direct transform ...
63  dir.execute(in, tmp);
64  // ... execute the inverse transform ...
65  inv.execute(tmp, out);
66 
67  // ... a well known fact of the FFT implementation in FFTW: it does
68  // not rescale the inverse transform by 1/N, so we must do that
69  // manually, ugh ...
70  for (auto subarray : out) {
71  for (auto vector : subarray) {
72  for (auto& element : vector) {
73  element /= nsamples;
74  }
75  }
76  }
77  // ... compare the input to the output, they should be nearly
78  // identical ...
80 }
81 
82 /// Verify that plans work for a batch of floating point to complex
83 /// and from complex to floating point.
84 template <typename precision_t>
85 void test_plan_real2complex() {
86  // Define some constants used to size the test, the size does not
87  // matter as much as the fact that we are testing multiple
88  // dimensions.
89  int const F = 2;
90  int const S = 32;
91  int const nsamples = 1 << 15;
92  // The tolerance (define as a fraction of 1.0) depends on the size
93  // of the test because the error in a FFT depends on the size of the
94  // vector:
95  // https://en.wikipedia.org/wiki/Fast_Fourier_transform#Accuracy
96  // we use a very rough approximation to the error here ...
97  int const tol = nsamples;
98 
99  // ... this case is all about testing the FFT transforms between
100  // multiple vectors of std::complex<> values ...
101  typedef std::complex<precision_t> complex;
102 
103  // ... we define an input array, an intermediate array to contain
104  // the FFT, and an output array that should (if things work Okay)
105  // contain the same contents as the input after a FFT and inverse
106  // FFT transformation ...
107  boost::multi_array<precision_t, 3> in(boost::extents[F][S][nsamples]);
108  boost::multi_array<complex, 3> tmp(boost::extents[F][S][nsamples]);
109  boost::multi_array<precision_t, 3> out(boost::extents[F][S][nsamples]);
110 
111  // ... initialize the input with a series of pretty straight forward
112  // triangular functions ...
113  for (auto subarray : in) {
114  for (auto vector : subarray) {
115  std::size_t h = nsamples / 2;
116  for (std::size_t i = 0; i != h; ++i) {
117  vector[i] = i - h / 4.0;
118  vector[i + h] = h / 4.0 - i;
119  }
120  }
121  }
122 
123  // ... create a plan to apply the DFT (direct, not inverse) from the
124  // in array to the tmp array ...
125  auto dir = jb::fftw::create_forward_plan(in, tmp);
126  // ... create a plan to apply the inverse DFT from the tmp array to
127  // the out array ...
128  auto inv = jb::fftw::create_backward_plan(tmp, out);
129 
130  // ... execute the direct transform ...
131  dir.execute(in, tmp);
132  // ... execute the inverse transform ...
133  inv.execute(tmp, out);
134  // ... a well known fact of the FFT implementation in FFTW: it does
135  // not rescale the inverse transform by 1/N, so we must do that
136  // manually, ugh ...
137  for (auto subarray : out) {
138  for (auto vector : subarray) {
139  for (auto& element : vector) {
140  element /= nsamples;
141  }
142  }
143  }
144  // ... compare the input to the output, they should be nearly
145  // identical ...
146  bool res = jb::testing::check_collection_close_enough(out, in, tol);
147  BOOST_CHECK_MESSAGE(res, "collections are not within tolerance=" << tol);
148 }
149 
150 /// A generic test parametric on the precision of the floating point
151 template <typename precision_t>
152 void test_plan_errors() {
153  // Define some constants used to size the test, the size does not
154  // matter as much as the fact that we are testing multiple
155  // dimensions.
156  int const F = 2;
157  int const S = 128;
158  int const nsamples = 1 << 15;
159 
160  // ... this case is all about testing the FFT transforms between
161  // multiple vectors of std::complex<> values ...
162  typedef std::complex<precision_t> complex;
163 
164  boost::multi_array<complex, 3> a0(boost::extents[F][S][nsamples]);
165  boost::multi_array<complex, 3> a1(boost::extents[F][S / 2][nsamples]);
166  boost::multi_array<complex, 3> a2(boost::extents[F][S][nsamples / 2]);
167  boost::multi_array<complex, 3> a3(boost::extents[F][S][0]);
168  boost::multi_array<complex, 3> a4(boost::extents[F][S][nsamples]);
169 
170  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(a0, a1), std::exception);
171  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(a0, a2), std::exception);
172  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(a0, a3), std::exception);
173  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(a3, a3), std::exception);
174  BOOST_CHECK_THROW(jb::fftw::create_backward_plan(a1, a0), std::exception);
175  BOOST_CHECK_THROW(jb::fftw::create_backward_plan(a2, a0), std::exception);
176  BOOST_CHECK_THROW(jb::fftw::create_backward_plan(a3, a0), std::exception);
177 
178  boost::multi_array<precision_t, 3> b1(boost::extents[F][S / 2][nsamples]);
179  boost::multi_array<precision_t, 3> b2(boost::extents[F][S][nsamples / 2]);
180  boost::multi_array<precision_t, 3> b3(boost::extents[F][S][0]);
181  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(b1, a0), std::exception);
182  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(b2, a0), std::exception);
183  BOOST_CHECK_THROW(jb::fftw::create_forward_plan(b3, a0), std::exception);
184  BOOST_CHECK_THROW(jb::fftw::create_backward_plan(a0, b1), std::exception);
185  BOOST_CHECK_THROW(jb::fftw::create_backward_plan(a0, b2), std::exception);
186  BOOST_CHECK_THROW(jb::fftw::create_backward_plan(a0, b3), std::exception);
187 
188  auto dir = jb::fftw::create_forward_plan(a0, a4);
189  BOOST_CHECK_THROW(dir.execute(a0, a1), std::exception);
190 }
191 
192 } // anonymous namespace
193 
194 /**
195  * @test Verify that we can create and execute plans that convert
196  * arrays of std::complex<double> to arrays of std::complex<double>.
197  */
198 BOOST_AUTO_TEST_CASE(fftw_plan_many_complex_double) {
199  test_plan_complex2complex<double>();
200 }
201 
202 /**
203  * @test Verify that we can create and execute plans that convert
204  * arrays of std::complex<float> to arrays of std::complex<float>.
205  */
206 BOOST_AUTO_TEST_CASE(fftw_plan_many_complex_float) {
207  test_plan_complex2complex<float>();
208 }
209 
210 /**
211  * @test Verify that we can create and execute plans that convert
212  * arrays of std::complex<long double> to arrays of std::complex<long double>.
213  */
214 BOOST_AUTO_TEST_CASE(fftw_plan_many_complex_long_double) {
215  if (RUNNING_ON_VALGRIND > 0) {
216  BOOST_TEST_MESSAGE("long double not supported by valgrind, skip test");
217  return;
218  }
219  test_plan_complex2complex<long double>();
220 }
221 
222 /**
223  * @test Verify that jb::fftw::create_foward_plan_1d() and
224  * jb::fftw::create_backward_plan() detect errors correctly for
225  * double.
226  */
227 BOOST_AUTO_TEST_CASE(fftw_plan_many_error_double) {
228  test_plan_errors<double>();
229 }
230 
231 /**
232  * @test Verify that jb::fftw::create_foward_plan_1d() and
233  * jb::fftw::create_backward_plan() detect errors correctly for
234  * float.
235  */
236 BOOST_AUTO_TEST_CASE(fftw_plan_many_error_float) {
237  test_plan_errors<float>();
238 }
239 
240 /**
241  * @test Verify that jb::fftw::create_foward_plan_1d() and
242  * jb::fftw::create_backward_plan() detect errors correctly for
243  * long double.
244  */
245 BOOST_AUTO_TEST_CASE(fftw_plan_many_error_long_double) {
246  if (RUNNING_ON_VALGRIND > 0) {
247  BOOST_TEST_MESSAGE("long double not supported by valgrind, skip test");
248  return;
249  }
250  test_plan_errors<long double>();
251 }
252 
253 /**
254  * @test Verify that we can create and execute plans that convert
255  * arrays of double to arrays of std::complex<double> and vice-versa.
256  */
257 BOOST_AUTO_TEST_CASE(fftw_plan_many_double) {
258  test_plan_real2complex<double>();
259 }
260 
261 /**
262  * @test Verify that we can create and execute plans that convert
263  * arrays of float to arrays of std::complex<float> and vice-versa.
264  */
265 BOOST_AUTO_TEST_CASE(fftw_plan_many_float) {
266  test_plan_real2complex<float>();
267 }
268 
269 /**
270  * @test Verify that we can create and execute plans that convert
271  * arrays of long double to arrays of std::complex<long double> and vice-versa.
272  */
273 BOOST_AUTO_TEST_CASE(fftw_plan_many_long_double) {
274  if (RUNNING_ON_VALGRIND > 0) {
275  BOOST_TEST_MESSAGE("long double not supported by valgrind, skip test");
276  return;
277  }
278  test_plan_real2complex<long double>();
279 }
plan< in_array_type, out_array_type > create_forward_plan(in_array_type const &in, out_array_type &out, int flags=default_plan_flags)
Create a plan to compute many DFTs given the input and output arrays.
Definition: plan.hpp:231
plan< in_array_type, out_array_type > create_backward_plan(in_array_type const &in, out_array_type &out, int flags=default_plan_flags)
Create a plan to compute many inverse DFT given the input and output arrays.
Definition: plan.hpp:261
bool check_collection_close_enough(collection_t const &a, collection_t const &b, int tol=1, int max_differences_printed=JB_TESTING_MAX_DIFFERENCES_PRINTED)
Given two collections of numbers of the same value type, find the differences that are out of a given...
std::size_t nsamples(container_type const &a)
Count the elements in the last dimension of a vector-like container.
BOOST_AUTO_TEST_CASE(fftw_plan_many_complex_double)