Commit ad9fa30cc62c375060594d0b6796e1008e5900c5
1 parent
3f24a1ab
Implement operators and add test code to illustrate smoothing of an image (#1731)
Showing
4 changed files
with
185 additions
and
22 deletions
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& GFft::operator=(const GFft& 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& GFft::operator+=(const GFft& 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& GFft::operator-=(const GFft& 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& GFft::operator*=(const GFft& 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& 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 | ... | ... |