Commit 4b980331d91dc158246eb715e7932241f16f846f

Authored by Jürgen Knödlseder
1 parent 79d739e6

Implement 2-dimensional Fast Fourier Transformation (#1731)

include/GFft.hpp
... ... @@ -66,7 +66,9 @@ public:
66 66  
67 67 // FFT access operators
68 68 std::complex<double>& operator()(const int& ix);
  69 + std::complex<double>& operator()(const int& ix, const int& iy);
69 70 const std::complex<double>& operator()(const int& ix) const;
  71 + const std::complex<double>& operator()(const int& ix, const int& iy) const;
70 72  
71 73 // Operators
72 74 GFft& operator=(const GFft& fft);
... ... @@ -197,7 +199,7 @@ std::string GFft::classname(void) const
197 199 /***********************************************************************//**
198 200 * @brief 1-dimensional FFT element access operator
199 201 *
200   - * @param[in] ix Element index [0,...,size()-1].
  202 + * @param[in] ix Element index [0,...,shape(0)-1].
201 203 * @return Reference to FFT element.
202 204 ***************************************************************************/
203 205 inline
... ... @@ -209,9 +211,24 @@ std::complex&lt;double&gt;&amp; GFft::operator()(const int&amp; ix)
209 211  
210 212  
211 213 /***********************************************************************//**
  214 + * @brief 2-dimensional FFT element access operator
  215 + *
  216 + * @param[in] ix Index in first dimension [0,...,shape(0)-1].
  217 + * @param[in] iy Index in second dimension [0,...,shape(1)-1].
  218 + * @return Reference to FFT element.
  219 + ***************************************************************************/
  220 +inline
  221 +std::complex<double>& GFft::operator()(const int& ix, const int& iy)
  222 +{
  223 + // Return array element
  224 + return *(m_data + ix + m_strides[1]*iy);
  225 +}
  226 +
  227 +
  228 +/***********************************************************************//**
212 229 * @brief 1-dimensional FFT element access operator (const variant)
213 230 *
214   - * @param[in] ix Element index [0,...,size()-1].
  231 + * @param[in] ix Element index [0,...,shape(0)-1].
215 232 * @return Const reference to FFT element.
216 233 ***************************************************************************/
217 234 inline
... ... @@ -223,6 +240,23 @@ const std::complex&lt;double&gt;&amp; GFft::operator()(const int&amp; ix) const
223 240  
224 241  
225 242 /***********************************************************************//**
  243 + * @brief 2-dimensional FFT element access operator (const variant)
  244 + *
  245 + * @param[in] ix Index in first dimension [0,...,shape(0)-1].
  246 + * @param[in] iy Index in second dimension [0,...,shape(1)-1].
  247 + * @return Const reference to FFT element.
  248 + ***************************************************************************/
  249 +inline
  250 +const std::complex<double>& GFft::operator()(const int& ix, const int& iy) const
  251 +{
  252 + // Return array element
  253 + return *(m_data + ix + m_strides[1]*iy);
  254 +}
  255 +
  256 +
  257 +
  258 +
  259 +/***********************************************************************//**
226 260 * @brief Return dimension of Fast Fourier Transformation
227 261 *
228 262 * @return Dimension of Fast Fourier Transformation.
... ...
src/model/GModelSpectralExpInvPlaw.cpp
... ... @@ -85,7 +85,7 @@ GModelSpectralExpInvPlaw::GModelSpectralExpInvPlaw(void) : GModelSpectral()
85 85 * @param[in] prefactor Name of prefactor parameter.
86 86 * @param[in] index Name of index parameter.
87 87 * @param[in] pivot Name of pivot parameter.
88   - * @param[in] cutoff Name of cutoff parameter.
  88 + * @param[in] lambda Name of cutoff parameter.
89 89 ***************************************************************************/
90 90 GModelSpectralExpInvPlaw::GModelSpectralExpInvPlaw(const std::string& type,
91 91 const std::string& prefactor,
... ...
src/numerics/GFft.cpp
... ... @@ -35,6 +35,8 @@
35 35 #include "GNdarray.hpp"
36 36  
37 37 /* __ Method name definitions ____________________________________________ */
  38 +#define G_FORWARD "GFft::forward(GNdarray&)"
  39 +#define G_BACKWARD "GFft::backward()"
38 40  
39 41  
40 42 /*==========================================================================
... ... @@ -277,7 +279,27 @@ GFft* GFft::clone(void) const
277 279 *
278 280 * @param[in] array N-dimensional array.
279 281 *
280   - * @todo Method should support N-dim arrays, only supports 1-dim so far
  282 + * GException::feature_not_implemented
  283 + * Array dimension is not supported.
  284 + *
  285 + * Performs discret Fast Fourier forward transformation for 1- and
  286 + * 2-dimensional arrays.
  287 + *
  288 + * For one dimensional arrays of length \f$N\f$ the transformation is given
  289 + * by
  290 + *
  291 + * \f[
  292 + * X_k = \sum_{n=0}^{N-1} z_n e^{-i 2 \pi \frac{k n}{N}}
  293 + * \f]
  294 + *
  295 + * and for two dimensional arrays of length \f$ M \times N\f$ the
  296 + * transformation is given by
  297 + *
  298 + * \f[
  299 + * X_{k,l} = \sum_{n=0}^{N-1} \left(
  300 + * \sum_{m=0}^{M-1} z_{m,n} e^{-i 2 \pi \frac{m k}{M}} \right)
  301 + * e^{-i 2 \pi \frac{n l}{N}}
  302 + * \f]
281 303 ***************************************************************************/
282 304 void GFft::forward(const GNdarray& array)
283 305 {
... ... @@ -286,26 +308,36 @@ void GFft::forward(const GNdarray&amp; array)
286 308  
287 309 // Continue only if there are elements in the data member
288 310 if (m_size > 0) {
289   -/*
290   - // Loop over all dimensions of array
291   - for (int d = 0; d < dim(); ++d) {
292 311  
293   - // Loop over all dimensions of array
294   - for (int i = 0; i < dim(); ++i) {
  312 + // Case A: array is 1-dimensional
  313 + if (array.dim() == 1) {
  314 + transform(m_data, 1, m_shape[0], m_wavetable[0], true);
  315 + }
295 316  
296   - // Skip
297   - if (d == i) {
298   - continue;
299   - }
  317 + // Case B: array is 2-dimensional
  318 + else if (array.dim() == 2) {
300 319  
301   - // Loop over all
302   - int n = shape()[d];
303   - int stride = strides()[d];
304   - int istart =
305   -*/
  320 + // Loop over rows
  321 + for (int row = 0; row < m_shape[1]; ++row) {
  322 + transform(m_data + row*m_strides[1], m_strides[0], m_shape[0],
  323 + m_wavetable[0], true);
  324 + }
306 325  
307   - // Perform FFT (is only correct for 1D for now)
308   - transform(m_data, 1, m_shape[0], m_wavetable[0], true);
  326 + // Loop over columns
  327 + for (int col = 0; col < m_shape[0]; ++col) {
  328 + transform(m_data + col*m_strides[0], m_strides[1], m_shape[1],
  329 + m_wavetable[1], true);
  330 + }
  331 +
  332 + }
  333 +
  334 + // ... otherwise throw an exception
  335 + else {
  336 + std::string msg = "Fast Fourier Transformation for "+
  337 + gammalib::str(array.dim())+"-dimensional array "
  338 + "not implemented.";
  339 + throw GException::feature_not_implemented(G_FORWARD, msg);
  340 + }
309 341  
310 342 } // endif: there were elements in the FFT data member
311 343  
... ... @@ -319,7 +351,28 @@ void GFft::forward(const GNdarray&amp; array)
319 351 *
320 352 * @return N-dimensional array.
321 353 *
322   - * @todo Method should support N-dim arrays, only supports 1-dim so far
  354 + * GException::feature_not_implemented
  355 + * Array dimension is not supported.
  356 + *
  357 + * Performs the inverse (or backward) discret Fast Fourier forward
  358 + * transformation for 1- and 2-dimensional arrays.
  359 + *
  360 + * For one dimensional arrays of length \f$N\f$ the transformation is given
  361 + * by
  362 + *
  363 + * \f[
  364 + * z_k = \frac{1}{N} \sum_{n=0}^{N-1} X_n e^{i 2 \pi \frac{k n}{N}}
  365 + * \f]
  366 + *
  367 + * and for two dimensional arrays of length \f$ M \times N\f$ the
  368 + * transformation is given by
  369 + *
  370 + * \f[
  371 + * z_{k,l} = \frac{1}{N} \sum_{n=0}^{N-1} \left(
  372 + * \frac{1}{M} \sum_{m=0}^{M-1} X_{m,n}
  373 + * e^{i 2 \pi \frac{m k}{M}} \right)
  374 + * e^{i 2 \pi \frac{n l}{N}}
  375 + * \f]
323 376 ***************************************************************************/
324 377 GNdarray GFft::backward(void) const
325 378 {
... ... @@ -327,24 +380,66 @@ GNdarray GFft::backward(void) const
327 380 GNdarray array(shape());
328 381  
329 382 // Continue only if there are elements in the array
330   - if (array.size() > 0) {
  383 + if (m_size > 0) {
331 384  
332 385 // Create copy of complex array
333   - std::complex<double>* data = new std::complex<double>[array.size()];
334   - for (int i = 0; i < array.size(); ++i) {
  386 + std::complex<double>* data = new std::complex<double>[m_size];
  387 + for (int i = 0; i < m_size; ++i) {
335 388 *(data+i) = *(m_data+i);
336 389 }
337 390  
338   - // Perform FFT (circumvent const correctness)
339   - const_cast<GFft*>(this)->transform(data, 1, m_shape[0], m_wavetable[0],
340   - false);
  391 + // Case A: array is 1-dimensional
  392 + if (array.dim() == 1) {
  393 +
  394 + // Perform backward transformation (circumvent const correctness)
  395 + const_cast<GFft*>(this)->transform(data, 1, m_shape[0], m_wavetable[0], false);
  396 +
  397 + // Get normalisation factor
  398 + const double norm = 1.0 / double(m_shape[0]);
  399 +
  400 + // Extract 1-dimensional array and normalise with 1/n
  401 + for (int i = 0; i < m_size; ++i) {
  402 + array(i) = (data+i)->real() * norm;
  403 + }
  404 +
  405 + }
  406 +
  407 + // Case B: array is 2-dimensional
  408 + else if (array.dim() == 2) {
341 409  
342   - // Get normalisation factor
343   - const double norm = 1.0 / double(array.size());
  410 + // Loop over rows
  411 + for (int row = 0; row < m_shape[1]; ++row) {
  412 + const_cast<GFft*>(this)->transform(data + row*m_strides[1], m_strides[0], m_shape[0],
  413 + m_wavetable[0], false);
  414 + }
  415 +
  416 + // Loop over columns
  417 + for (int col = 0; col < m_shape[0]; ++col) {
  418 + const_cast<GFft*>(this)->transform(data + col*m_strides[0], m_strides[1], m_shape[1],
  419 + m_wavetable[1], false);
  420 + }
  421 +
  422 + // Get normalisation factor
  423 + const double norm = 1.0 / double(m_shape[0]*m_shape[1]);
  424 +
  425 + // Extract 2-dimensional array and normalise with 1/(n*m)
  426 + for (int i = 0; i < m_size; ++i) {
  427 + array(i) = (data+i)->real() * norm;
  428 + }
344 429  
345   - // Extract N-dimensional array and normalise with 1/n
346   - for (int i = 0; i < array.size(); ++i) {
347   - array(i) = (data+i)->real() * norm;
  430 + }
  431 +
  432 + // ... otherwise throw an exception
  433 + else {
  434 +
  435 + // Delete copy of complex array
  436 + delete [] data;
  437 +
  438 + // Throw exception
  439 + std::string msg = "Fast Fourier Transformation for "+
  440 + gammalib::str(array.dim())+"-dimensional array "
  441 + "not implemented.";
  442 + throw GException::feature_not_implemented(G_BACKWARD, msg);
348 443 }
349 444  
350 445 // Delete copy of complex array
... ... @@ -501,15 +596,9 @@ void GFft::set_data(const GNdarray&amp; array)
501 596 *(m_data+i) = std::complex<double>(array(i), 0.0);
502 597 }
503 598  
504   - // Save shape
505   - m_shape = array.shape();
506   -
507   - // Set strides
508   - int stride = 1;
509   - for (int i = 0; i < m_shape.size(); ++i) {
510   - m_strides.push_back(stride);
511   - stride *= m_shape[i];
512   - }
  599 + // Save shape and strides
  600 + m_shape = array.shape();
  601 + m_strides = array.strides();
513 602  
514 603 // Set trigonometric coefficients
515 604 for (int i = 0; i < m_shape.size(); ++i) {
... ... @@ -1319,30 +1408,18 @@ void GFft::factorn(std::complex&lt;double&gt;* in,
1319 1408 for (int e1 = 1; e1 < factor; ++e1) {
1320 1409  
1321 1410 // Get x
1322   - double x_real = (in+istride*(i + e1 * m))->real();
1323   - double x_imag = (in+istride*(i + e1 * m))->imag();
  1411 + std::complex<double> x = *(in+istride*(i + e1 * m));
1324 1412  
1325   - // Get w
1326   - double w_real, w_imag;
1327   -
1328   - // Compute indices
  1413 + // Compute index
1329 1414 int twiddle = index + (e1-1)*q + k-1;
1330 1415  
1331   - // Set trigonometric coefficients for forward transform
1332   - if (sign == -1) {
1333   - w_real = wavetable[twiddle].real();
1334   - w_imag = wavetable[twiddle].imag();
1335   - }
1336   - // ... otherwise set trigonometric coefficients for backward
1337   - // tranform: w -> conjugate(w)
1338   - else {
1339   - w_real = wavetable[twiddle].real();
1340   - w_imag = -wavetable[twiddle].imag();
1341   - }
  1416 + // Get w
  1417 + std::complex<double> w = (sign == -1)
  1418 + ? wavetable[twiddle]
  1419 + : std::conj(wavetable[twiddle]);
1342 1420  
1343   - //
1344   - *(out+ostride*(j + e1 * p_1)) = std::complex<double>(w_real * x_real - w_imag * x_imag,
1345   - w_real * x_imag + w_imag * x_real);
  1421 + // Compute out = w * x
  1422 + *(out+ostride*(j + e1 * p_1)) = w * x;
1346 1423  
1347 1424 } // endfor: e1
1348 1425  
... ...
src/numerics/GFftWavetable.cpp
... ... @@ -383,7 +383,7 @@ void GFftWavetable::set_members(const int&amp; n)
383 383 *
384 384 * @exception GException::invalid_argument
385 385 * Non positive array length specified
386   - * @expection GException::invalid_value
  386 + * @exception GException::invalid_value
387 387 * Product of factorisation factors are not equal to array length
388 388 *
389 389 * Computes the factorisation
... ...
src/numerics/GNdarray.cpp
... ... @@ -777,6 +777,7 @@ bool GNdarray::has_same_shape(const GNdarray&amp; array) const
777 777 /***********************************************************************//**
778 778 * @brief Throw exception if array shapes differ
779 779 *
  780 + * @param[in] method Method that throws exception.
780 781 * @param[in] array Array.
781 782 ***************************************************************************/
782 783 void GNdarray::require_same_shape(const std::string& method,
... ...
src/vo/GVOTable.cpp
... ... @@ -120,7 +120,7 @@ GVOTable::~GVOTable(void)
120 120 /***********************************************************************//**
121 121 * @brief Assignment operator
122 122 *
123   - * @param[in] VO table.
  123 + * @param[in] votable VO table.
124 124 * @return VO table.
125 125 ***************************************************************************/
126 126 GVOTable& GVOTable::operator=(const GVOTable& votable)
... ...
test/test_GNumerics.cpp
... ... @@ -282,7 +282,7 @@ void TestGNumerics::test_ndarray(void)
282 282 ***************************************************************************/
283 283 void TestGNumerics::test_fft(void)
284 284 {
285   - // Loop over array lengths
  285 + // Test 1-dimensional FFT
286 286 for (int n = 2; n < 12; ++n) {
287 287  
288 288 // Skip n=8,9,10 since they can be factorised
... ... @@ -303,25 +303,32 @@ void TestGNumerics::test_fft(void)
303 303 GNdarray back = fft.backward();
304 304  
305 305 // Check FFT results
306   - check_fft(array, fft, back);
  306 + check_fft1(array, fft, back);
307 307  
308 308 } // endfor: looped over array lengths
309 309  
  310 + // Test 2-dimensional FFT
  311 + GNdarray array2(8,9);
  312 + array2(4,4) = 1.0;
  313 + GFft fft2(array2);
  314 + GNdarray back2 = fft2.backward();
  315 + check_fft2(array2, fft2, back2);
  316 +
310 317 // Exit test
311 318 return;
312 319 }
313 320  
314 321  
315 322 /***********************************************************************//**
316   - * @brief Check Fast Fourier Transform
  323 + * @brief Check 1-dimensional Fast Fourier Transform
317 324 *
318 325 * @param[in] array Input array.
319 326 * @param[in] fft Fast Fourier Transform of input array.
320 327 * @param[in] back Backward transform of FFT (should be equal to input array)
321 328 ***************************************************************************/
322   -void TestGNumerics::check_fft(const GNdarray& array,
323   - const GFft& fft,
324   - const GNdarray& back)
  329 +void TestGNumerics::check_fft1(const GNdarray& array,
  330 + const GFft& fft,
  331 + const GNdarray& back)
325 332 {
326 333 // Get normalisation factor
327 334 double norm = 1.0 / double(array.size());
... ... @@ -350,6 +357,53 @@ void TestGNumerics::check_fft(const GNdarray&amp; array,
350 357  
351 358  
352 359 /***********************************************************************//**
  360 + * @brief Check 2-dimensional Fast Fourier Transform
  361 + *
  362 + * @param[in] array Input array.
  363 + * @param[in] fft Fast Fourier Transform of input array.
  364 + * @param[in] back Backward transform of FFT (should be equal to input array)
  365 + ***************************************************************************/
  366 +void TestGNumerics::check_fft2(const GNdarray& array,
  367 + const GFft& fft,
  368 + const GNdarray& back)
  369 +{
  370 + // Get normalisation factor
  371 + double normM = 1.0 / double(array.shape()[0]);
  372 + double normN = 1.0 / double(array.shape()[1]);
  373 +
  374 + // Compute reference value and compare with FFT result
  375 + for (int k = 0; k < array.shape()[0]; ++k) {
  376 + for (int l = 0; l < array.shape()[1]; ++l) {
  377 + double ref_real = 0.0;
  378 + double ref_imag = 0.0;
  379 + for (int n = 0; n < array.shape()[1]; ++n) {
  380 + double sum_real = 0.0;
  381 + double sum_imag = 0.0;
  382 + for (int m = 0; m < array.shape()[0]; ++m) {
  383 + double x = -gammalib::twopi * m * k * normM;
  384 + sum_real += array(m,n) * std::cos(x);
  385 + sum_imag += array(m,n) * std::sin(x);
  386 + }
  387 + double y = -gammalib::twopi * n * l * normN;
  388 + ref_real += (sum_real * std::cos(y) - sum_imag * std::sin(y));
  389 + ref_imag += (sum_real * std::sin(y) + sum_imag * std::cos(y));
  390 + }
  391 + std::complex<double> ref(ref_real, ref_imag);
  392 + test_value(fft(k,l), ref);
  393 + }
  394 + }
  395 +
  396 + // Test backward transformation
  397 + for (int k = 0; k < array.size(); ++k) {
  398 + test_value(back(k), array(k));
  399 + }
  400 +
  401 + // Exit test
  402 + return;
  403 +}
  404 +
  405 +
  406 +/***********************************************************************//**
353 407 * @brief Test model parameter handling
354 408 ***************************************************************************/
355 409 void TestGNumerics::test_integral(void)
... ...
test/test_GNumerics.hpp
... ... @@ -70,9 +70,12 @@ public:
70 70 virtual std::string classname(void) const { return "TestGNumerics"; }
71 71 void test_ndarray(void);
72 72 void test_fft(void);
73   - void check_fft(const GNdarray& array,
74   - const GFft& fft,
75   - const GNdarray& back);
  73 + void check_fft1(const GNdarray& array,
  74 + const GFft& fft,
  75 + const GNdarray& back);
  76 + void check_fft2(const GNdarray& array,
  77 + const GFft& fft,
  78 + const GNdarray& back);
76 79 void test_integral(void);
77 80 void test_romberg_integration(void);
78 81 void test_adaptive_simpson_integration(void);
... ...