Commit ad9fa30cc62c375060594d0b6796e1008e5900c5

Authored by Jürgen Knödlseder
1 parent 3f24a1ab

Implement operators and add test code to illustrate smoothing of an image (#1731)

include/GFft.hpp
... ... @@ -96,6 +96,8 @@ protected:
96 96 void copy_members(const GFft& fft);
97 97 void free_members(void);
98 98 void set_data(const GNdarray& array);
  99 + bool has_same_shape(const GFft& fft) const;
  100 + void require_same_shape(const std::string& method, const GFft& fft) const;
99 101  
100 102 // Low-level FFT methods
101 103 void transform(std::complex<double>* data,
... ...
pyext/GFft.i
... ... @@ -114,6 +114,33 @@ public:
114 114 }
115 115 (*self)(index) = value;
116 116 }
  117 + GFft __add__(const GFft &a) {
  118 + return (*self) + a;
  119 + }
  120 + GFft __sub__(const GFft &a) {
  121 + return (*self) - a;
  122 + }
  123 + GFft __mul__(const GFft &a) {
  124 + return (*self) * a;
  125 + }
  126 + // Python 2.x
  127 + GFft __div__(const GFft &a) {
  128 + return (*self) / a;
  129 + }
  130 + // Python 3.x
  131 + GFft __truediv__(const GFft &a) {
  132 + return (*self) / a;
  133 + }
  134 + // Python 2.x operator/=
  135 + GFft __idiv__(const GFft& a) {
  136 + self->operator/=(a);
  137 + return (*self);
  138 + }
  139 + // Python 3.x operator/=
  140 + GFft __itruediv__(const GFft& a) {
  141 + self->operator/=(a);
  142 + return (*self);
  143 + }
117 144 GFft copy() {
118 145 return (*self);
119 146 }
... ...
src/numerics/GFft.cpp
... ... @@ -35,6 +35,10 @@
35 35 #include "GNdarray.hpp"
36 36  
37 37 /* __ Method name definitions ____________________________________________ */
  38 +#define G_OP_ADD "GFft::operator+=(GFft&)"
  39 +#define G_OP_SUB "GFft::operator-=(GFft&)"
  40 +#define G_OP_MUL "GFft::operator*=(GFft&)"
  41 +#define G_OP_DIV "GFft::operator/=(GFft&)"
38 42 #define G_FORWARD "GFft::forward(GNdarray&)"
39 43 #define G_BACKWARD "GFft::backward()"
40 44  
... ... @@ -148,13 +152,18 @@ GFft&amp; GFft::operator=(const GFft&amp; fft)
148 152 * @param[in] fft Fast Fourier Transform.
149 153 * @return Fast Fourier Transform.
150 154 *
151   - * Adds a Fast Fourier Transform to another Fast Fourier Transform.
152   - *
153   - * @todo Implement operator
  155 + * Adds a Fast Fourier Transform to another Fast Fourier Transform. The
  156 + * method requires that both FFTs have the same dimension.
154 157 ***************************************************************************/
155 158 GFft& GFft::operator+=(const GFft& fft)
156 159 {
157   - // TODO: Implement operator
  160 + // Throw an exception if the FFTs have not the same shape
  161 + require_same_shape(G_OP_ADD, fft);
  162 +
  163 + // Add elements
  164 + for (int i = 0; i < m_size; ++i) {
  165 + *(m_data+i) += *(fft.m_data+i);
  166 + }
158 167  
159 168 // Return Fast Fourier Transform
160 169 return *this;
... ... @@ -168,12 +177,17 @@ GFft&amp; GFft::operator+=(const GFft&amp; fft)
168 177 * @return Fast Fourier transform.
169 178 *
170 179 * Subtracts a Fast Fourier Transform from another Fast Fourier Transform.
171   - *
172   - * @todo Implement operator
  180 + * The method requires that both FFTs have the same dimension.
173 181 ***************************************************************************/
174 182 GFft& GFft::operator-=(const GFft& fft)
175 183 {
176   - // TODO: Implement operator
  184 + // Throw an exception if the FFTs have not the same shape
  185 + require_same_shape(G_OP_SUB, fft);
  186 +
  187 + // Subtract elements
  188 + for (int i = 0; i < m_size; ++i) {
  189 + *(m_data+i) -= *(fft.m_data+i);
  190 + }
177 191  
178 192 // Return Fast Fourier Transform
179 193 return *this;
... ... @@ -187,12 +201,17 @@ GFft&amp; GFft::operator-=(const GFft&amp; fft)
187 201 * @return Fast Fourier transform.
188 202 *
189 203 * Multiplies a Fast Fourier Transform to another Fast Fourier Transform.
190   - *
191   - * @todo Implement operator
  204 + * The method requires that both FFTs have the same dimension.
192 205 ***************************************************************************/
193 206 GFft& GFft::operator*=(const GFft& fft)
194 207 {
195   - // TODO: Implement operator
  208 + // Throw an exception if the FFTs have not the same shape
  209 + require_same_shape(G_OP_MUL, fft);
  210 +
  211 + // Multiply elements
  212 + for (int i = 0; i < m_size; ++i) {
  213 + *(m_data+i) *= *(fft.m_data+i);
  214 + }
196 215  
197 216 // Return Fast Fourier Transform
198 217 return *this;
... ... @@ -206,12 +225,17 @@ GFft&amp; GFft::operator*=(const GFft&amp; fft)
206 225 * @return Fast Fourier transform.
207 226 *
208 227 * Divides a Fast Fourier Transform by another Fast Fourier Transform.
209   - *
210   - * @todo Implement operator
  228 + * The method requires that both FFTs have the same dimension.
211 229 ***************************************************************************/
212 230 GFft& GFft::operator/=(const GFft& fft)
213 231 {
214   - // TODO: Implement operator
  232 + // Throw an exception if the FFTs have not the same shape
  233 + require_same_shape(G_OP_DIV, fft);
  234 +
  235 + // Divide elements
  236 + for (int i = 0; i < m_size; ++i) {
  237 + *(m_data+i) /= *(fft.m_data+i);
  238 + }
215 239  
216 240 // Return Fast Fourier Transform
217 241 return *this;
... ... @@ -614,6 +638,75 @@ void GFft::set_data(const GNdarray&amp; array)
614 638  
615 639  
616 640 /***********************************************************************//**
  641 + * @brief Check if FFT has the same shape
  642 + *
  643 + * @param[in] fft Fast Fourier Transform.
  644 + * @return True if the FFT has the same shape.
  645 + ***************************************************************************/
  646 +bool GFft::has_same_shape(const GFft& fft) const
  647 +{
  648 + // Check if the array has the same dimension
  649 + bool identity = m_shape.size() == fft.m_shape.size();
  650 +
  651 + // Check if the shape is identical. Break as soon as one shape value
  652 + // differs.
  653 + if (identity) {
  654 + for (int i = 0; i < m_shape.size(); ++i) {
  655 + if (m_shape[i] != fft.m_shape[i]) {
  656 + identity = false;
  657 + break;
  658 + }
  659 + }
  660 + }
  661 +
  662 + // Return result
  663 + return identity;
  664 +}
  665 +
  666 +
  667 +/***********************************************************************//**
  668 + * @brief Throw exception if FFT shapes differ
  669 + *
  670 + * @param[in] method Method that throws exception.
  671 + * @param[in] fft Fast Fourier Transform.
  672 + *
  673 + * @exception GException::invalid_argument
  674 + * FFT shapes differ.
  675 + ***************************************************************************/
  676 +void GFft::require_same_shape(const std::string& method,
  677 + const GFft& fft) const
  678 +{
  679 + // If the shape differs then throw an exception
  680 + if (!has_same_shape(fft)) {
  681 +
  682 + // Compose exception message
  683 + std::string msg = "Incompatible FFT dimensions (";
  684 + for (int i = 0; i < m_shape.size(); ++i) {
  685 + if (i > 0) {
  686 + msg += ", ";
  687 + }
  688 + msg += gammalib::str(m_shape[i]);
  689 + }
  690 + msg += ") <=> (";
  691 + for (int i = 0; i < fft.m_shape.size(); ++i) {
  692 + if (i > 0) {
  693 + msg += ", ";
  694 + }
  695 + msg += gammalib::str(fft.m_shape[i]);
  696 + }
  697 + msg += ").";
  698 +
  699 + // Throw exception
  700 + throw GException::invalid_argument(method, msg);
  701 +
  702 + } // endif: array shapes differed
  703 +
  704 + // Return
  705 + return;
  706 +}
  707 +
  708 +
  709 +/***********************************************************************//**
617 710 * @brief Perform Fast Fourier Transform
618 711 *
619 712 * @param[in] data Pointer to complex array to be transformed.
... ...
test/test_GNumerics.py
1 1 # ==========================================================================
2 2 # This module performs unit tests for the GammaLib numerics module.
3 3 #
4   -# Copyright (C) 2012-2015 Juergen Knoedlseder
  4 +# Copyright (C) 2012-2016 Juergen Knoedlseder
5 5 #
6 6 # This program is free software: you can redistribute it and/or modify
7 7 # it under the terms of the GNU General Public License as published by
... ... @@ -25,12 +25,12 @@ import gammalib
25 25 # ======================================= #
26 26 class Test(gammalib.GPythonTestSuite):
27 27 """
28   - Test class for GammaLib numerics module.
  28 + Test class for GammaLib numerics module
29 29 """
30 30 # Constructor
31 31 def __init__(self):
32 32 """
33   - Constructor.
  33 + Constructor
34 34 """
35 35 # Call base class constructor
36 36 gammalib.GPythonTestSuite.__init__(self)
... ... @@ -41,21 +41,62 @@ class Test(gammalib.GPythonTestSuite):
41 41 # Set test functions
42 42 def set(self):
43 43 """
44   - Set all test functions.
  44 + Set all test functions
45 45 """
46 46 # Set test name
47   - self.name("numerics")
  47 + self.name('numerics')
48 48  
49 49 # Append tests
50   - self.append(self.test, "Numerics module dummy test")
  50 + self.append(self.test_fft, 'Test GFft')
51 51  
52 52 # Return
53 53 return
54 54  
55   - # Test function
56   - def test(self):
  55 + # Test Fast Fourier Transform
  56 + def test_fft(self):
57 57 """
58   - Test function.
  58 + Test function
59 59 """
  60 + # Allocate and set 2-d array
  61 + array = gammalib.GNdarray(10, 10)
  62 + for i in range(3,7):
  63 + array[i,i] = 1.0
  64 + ref = array.sum()
  65 +
  66 + # Allocate and set 2-d kernel. The kernel needs to be normalized
  67 + # to unity and the centre of the kernel needs to be at pixel [0,0],
  68 + # and it needs to be wraped around to negative indices
  69 + kernel = gammalib.GNdarray(10, 10)
  70 + kernel[0,0] = 0.4
  71 + # 0.4
  72 + kernel[0,1] = 0.1
  73 + kernel[1,0] = 0.1
  74 + kernel[0,9] = 0.1
  75 + kernel[9,0] = 0.1
  76 + # 0.2
  77 + kernel[1,1] = 0.05
  78 + kernel[9,1] = 0.05
  79 + kernel[9,9] = 0.05
  80 + kernel[1,9] = 0.05
  81 +
  82 + # Smooth 2-d array using FFT
  83 + fft_array = gammalib.GFft(array)
  84 + fft_kernel = gammalib.GFft(kernel)
  85 + fft_smooth = fft_array * fft_kernel
  86 +
  87 + # Backtransform
  88 + smooth = fft_smooth.backward()
  89 +
  90 + # Test sum
  91 + sum = smooth.sum()
  92 + self.test_value(sum, ref)
  93 +
  94 + # Store in sky map
  95 + map = gammalib.GSkyMap('CAR','CEL',0.0,0.0,-1.0,1.0,10,10)
  96 + for iy in range(10):
  97 + for ix in range(10):
  98 + map[ix+iy*10] = smooth[ix,iy]
  99 + map.save('test_fft.fits', True)
  100 +
60 101 # Return
61 102 return
... ...