BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
Convolve.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Device/Resolution/Convolve.cpp
6 //! @brief Implements class Convolve.
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
16 #include <iostream>
17 #include <sstream>
18 #include <stdexcept> // need overlooked by g++ 5.4
19 
20 Convolve::Convolve() : m_mode(FFTW_UNDEFINED)
21 {
22  // storing favorite fftw3 prime factors
23  const size_t FFTW_FACTORS[] = {13, 11, 7, 5, 3, 2};
24  m_implemented_factors.assign(FFTW_FACTORS,
25  FFTW_FACTORS + sizeof(FFTW_FACTORS) / sizeof(FFTW_FACTORS[0]));
26 }
27 
29  : h_src(0)
30  , w_src(0)
31  , h_kernel(0)
32  , w_kernel(0)
33  , w_fftw(0)
34  , h_fftw(0)
35  , in_src(0)
36  , out_src(0)
37  , in_kernel(0)
38  , out_kernel(0)
39  , dst_fft(0)
40  , h_dst(0)
41  , w_dst(0)
42  ,
43  // dst(0),
44  h_offset(0)
45  , w_offset(0)
46  , p_forw_src(nullptr)
47  , p_forw_kernel(nullptr)
48  , p_back(nullptr)
49 {
50 }
51 
53 {
54  clear();
55 }
56 
58 {
59  h_src = 0;
60  w_src = 0;
61  h_kernel = 0;
62  w_kernel = 0;
63  if (in_src)
64  delete[] in_src;
65  in_src = 0;
66 
67  if (out_src)
68  fftw_free((fftw_complex*)out_src);
69  out_src = 0;
70 
71  if (in_kernel)
72  delete[] in_kernel;
73  in_kernel = 0;
74 
75  if (out_kernel)
76  fftw_free((fftw_complex*)out_kernel);
77  out_kernel = 0;
78 
79  if (dst_fft)
80  delete[] dst_fft;
81  dst_fft = 0;
82 
83  // if(dst) delete[] dst;
84  // dst=0;
85 
86  h_offset = 0;
87  w_offset = 0;
88 
89  if (p_forw_src != nullptr)
90  fftw_destroy_plan(p_forw_src);
91  if (p_forw_kernel != nullptr)
92  fftw_destroy_plan(p_forw_kernel);
93  if (p_back != nullptr)
94  fftw_destroy_plan(p_back);
95 
96  fftw_cleanup();
97 }
98 
99 /* ************************************************************************* */
100 // convolution in 2d
101 /* ************************************************************************* */
102 void Convolve::fftconvolve(const double2d_t& source, const double2d_t& kernel, double2d_t& result)
103 {
104  // set default convolution mode, if not defined
105  if (m_mode == FFTW_UNDEFINED)
107 
108  int h_src = (int)source.size();
109  int w_src = (int)(source.size() ? source[0].size() : 0);
110  int h_kernel = (int)kernel.size();
111  int w_kernel = (kernel.size() ? (int)kernel[0].size() : 0);
112 
113  // initialisation
114  init(h_src, w_src, h_kernel, w_kernel);
115  // Compute the circular convolution
116  fftw_circular_convolution(source, kernel);
117 
118  // results
119  result.clear();
120  result.resize(ws.h_dst);
121  for (int i = 0; i < ws.h_dst; i++) {
122  result[i].resize(ws.w_dst, 0);
123  for (int j = 0; j < ws.w_dst; j++) {
124  // result[i][j]=ws.dst[i*ws.w_dst+j];
126  result[i][j] = ws.dst_fft[((i + int(ws.h_kernel / 2.0)) % ws.h_fftw) * ws.w_fftw
127  + (j + int(ws.w_kernel / 2.0)) % ws.w_fftw];
128  } else {
129  result[i][j] = ws.dst_fft[(i + ws.h_offset) * ws.w_fftw + j + ws.w_offset];
130  }
131  }
132  }
133 }
134 
135 /* ************************************************************************* */
136 // convolution in 1d
137 /* ************************************************************************* */
138 void Convolve::fftconvolve(const double1d_t& source, const double1d_t& kernel, double1d_t& result)
139 {
140  // we simply create 2d arrays with length of first dimension equal to 1, and call 2d convolution
141  double2d_t source2d, kernel2d;
142  source2d.push_back(source);
143  kernel2d.push_back(kernel);
144 
145  double2d_t result2d;
146  fftconvolve(source2d, kernel2d, result2d);
147  if (result2d.size() != 1)
148  throw std::runtime_error("Convolve::fftconvolve -> Panic in 1d");
149  result = result2d[0];
150 }
151 
152 /* ************************************************************************* */
153 // initialise input and output arrays for fast Fourier transformation
154 /* ************************************************************************* */
155 void Convolve::init(int h_src, int w_src, int h_kernel, int w_kernel)
156 {
157  if (!h_src || !w_src || !h_kernel || !w_kernel) {
158  std::ostringstream os;
159  os << "Convolve::init() -> Panic! Wrong dimensions " << h_src << " " << w_src << " "
160  << h_kernel << " " << w_kernel << std::endl;
161  throw std::runtime_error(os.str());
162  }
163 
164  ws.clear();
165  ws.h_src = h_src;
166  ws.w_src = w_src;
167  ws.h_kernel = h_kernel;
168  ws.w_kernel = w_kernel;
169  switch (m_mode) {
170  case FFTW_LINEAR_FULL:
171  // Full Linear convolution
172  ws.h_fftw = find_closest_factor(h_src + h_kernel - 1);
173  ws.w_fftw = find_closest_factor(w_src + w_kernel - 1);
174  ws.h_dst = h_src + h_kernel - 1;
175  ws.w_dst = w_src + w_kernel - 1;
176  // Here we just keep the first [0:h_dst-1 ; 0:w_dst-1] real part elements of out_src
177  ws.h_offset = 0;
178  ws.w_offset = 0;
179  break;
181  // Same Linear convolution
182  ws.h_fftw = h_src + int(h_kernel / 2.0);
183  ws.w_fftw = w_src + int(w_kernel / 2.0);
184  ws.h_dst = h_src;
185  ws.w_dst = w_src;
186  // Here we just keep the first [h_filt/2:h_filt/2+h_dst-1 ; w_filt/2:w_filt/2+w_dst-1]
187  // real part elements of out_src
188  ws.h_offset = int(ws.h_kernel / 2.0);
189  ws.w_offset = int(ws.w_kernel / 2.0);
190  break;
191  case FFTW_LINEAR_SAME:
192  // Same Linear convolution
193  ws.h_fftw = find_closest_factor(h_src + int(h_kernel / 2.0));
194  ws.w_fftw = find_closest_factor(w_src + int(w_kernel / 2.0));
195  ws.h_dst = h_src;
196  ws.w_dst = w_src;
197  // Here we just keep the first [h_filt/2:h_filt/2+h_dst-1 ; w_filt/2:w_filt/2+w_dst-1]
198  // real part elements of out_src
199  ws.h_offset = int(ws.h_kernel / 2.0);
200  ws.w_offset = int(ws.w_kernel / 2.0);
201  break;
202  case FFTW_LINEAR_VALID:
203  // Valid Linear convolution
204  if (ws.h_kernel > ws.h_src || ws.w_kernel > ws.w_src) {
205  ws.h_fftw = 0;
206  ws.w_fftw = 0;
207  ws.h_dst = 0;
208  ws.w_dst = 0;
209  std::cout << "The 'valid' convolution results in an empty matrix" << std::endl;
210  throw std::runtime_error("The 'valid' convolution results in an empty matrix");
211  } else {
212  ws.h_fftw = find_closest_factor(h_src);
213  ws.w_fftw = find_closest_factor(w_src);
214  ws.h_dst = h_src - h_kernel + 1;
215  ws.w_dst = w_src - w_kernel + 1;
216  }
217  // Here we just take [h_dst x w_dst] elements starting at [h_kernel-1;w_kernel-1]
218  ws.h_offset = ws.h_kernel - 1;
219  ws.w_offset = ws.w_kernel - 1;
220  break;
221  case FFTW_CIRCULAR_SAME:
223  // Circular convolution, modulo N
224  // We don't padd with zeros because if we do, we need to padd with at least h_kernel/2;
225  // w_kernel/2 elements plus the wrapp around, which in facts leads to too much
226  // computations compared to the gain obtained with the optimal size
227  ws.h_fftw = h_src;
228  ws.w_fftw = w_src;
229  ws.h_dst = h_src;
230  ws.w_dst = w_src;
231  // We copy the first [0:h_dst-1 ; 0:w_dst-1] real part elements of out_src
232  ws.h_offset = 0;
233  ws.w_offset = 0;
234  break;
235  default:
236  std::cout
237  << "Unrecognized convolution mode, possible modes are "
238  << "FFTW_LINEAR_FULL, FFTW_LINEAR_SAME, FFTW_LINEAR_SAME_UNPADDED, FFTW_LINEAR_VALID, "
239  << "FFTW_CIRCULAR_SAME, FFTW_CIRCULAR_SHIFTED " << std::endl;
240  break;
241  }
242 
243  ws.in_src = new double[ws.h_fftw * ws.w_fftw];
244  ws.out_src = (double*)fftw_malloc(sizeof(fftw_complex) * ws.h_fftw * (ws.w_fftw / 2 + 1));
245  ws.in_kernel = new double[ws.h_fftw * ws.w_fftw];
246  ws.out_kernel = (double*)fftw_malloc(sizeof(fftw_complex) * ws.h_fftw * (ws.w_fftw / 2 + 1));
247 
248  ws.dst_fft = new double[ws.h_fftw * ws.w_fftw];
249  // ws.dst = new double[ws.h_dst * ws.w_dst];
250 
251  // Initialization of the plans
252  ws.p_forw_src = fftw_plan_dft_r2c_2d(ws.h_fftw, ws.w_fftw, ws.in_src, (fftw_complex*)ws.out_src,
253  FFTW_ESTIMATE);
254  if (ws.p_forw_src == nullptr)
255  throw std::runtime_error("Convolve::init() -> Error! Can't initialise p_forw_src plan.");
256 
257  ws.p_forw_kernel = fftw_plan_dft_r2c_2d(ws.h_fftw, ws.w_fftw, ws.in_kernel,
258  (fftw_complex*)ws.out_kernel, FFTW_ESTIMATE);
259  if (ws.p_forw_kernel == nullptr)
260  throw std::runtime_error("Convolve::init() -> Error! Can't initialise p_forw_kernel plan.");
261 
262  // The backward FFT takes ws.out_kernel as input
263  ws.p_back = fftw_plan_dft_c2r_2d(ws.h_fftw, ws.w_fftw, (fftw_complex*)ws.out_kernel, ws.dst_fft,
264  FFTW_ESTIMATE);
265  if (ws.p_back == nullptr)
266  throw std::runtime_error("Convolve::init() -> Error! Can't initialise p_back plan.");
267 }
268 
269 /* ************************************************************************* */
270 // initialise input and output arrays for fast Fourier transformation
271 /* ************************************************************************* */
272 
274 {
275  if (ws.h_fftw <= 0 || ws.w_fftw <= 0)
276  throw std::runtime_error("Convolve::fftw_convolve() -> Panic! Initialisation is missed.");
277 
278  double *ptr, *ptr_end, *ptr2;
279 
280  // Reset the content of ws.in_src
281  for (ptr = ws.in_src, ptr_end = ws.in_src + ws.h_fftw * ws.w_fftw; ptr != ptr_end; ++ptr)
282  *ptr = 0.0;
283  for (ptr = ws.in_kernel, ptr_end = ws.in_kernel + ws.h_fftw * ws.w_fftw; ptr != ptr_end; ++ptr)
284  *ptr = 0.0;
285 
286  // Then we build our periodic signals
287  // ptr = src;
288  for (int i = 0; i < ws.h_src; ++i)
289  for (int j = 0; j < ws.w_src; ++j, ++ptr)
290  ws.in_src[(i % ws.h_fftw) * ws.w_fftw + (j % ws.w_fftw)] += src[i][j];
291 
292  // ptr = kernel;
293  for (int i = 0; i < ws.h_kernel; ++i)
294  for (int j = 0; j < ws.w_kernel; ++j, ++ptr)
295  ws.in_kernel[(i % ws.h_fftw) * ws.w_fftw + (j % ws.w_fftw)] += kernel[i][j];
296 
297  // And we compute their packed FFT
298  fftw_execute(ws.p_forw_src);
299  fftw_execute(ws.p_forw_kernel);
300 
301  // Compute the element-wise product on the packed terms
302  // Let's put the element wise products in ws.in_kernel
303  double re_s, im_s, re_k, im_k;
304  for (ptr = ws.out_src, ptr2 = ws.out_kernel,
305  ptr_end = ws.out_src + 2 * ws.h_fftw * (ws.w_fftw / 2 + 1);
306  ptr != ptr_end; ++ptr, ++ptr2) {
307  re_s = *ptr;
308  im_s = *(++ptr);
309  re_k = *ptr2;
310  im_k = *(++ptr2);
311  *(ptr2 - 1) = re_s * re_k - im_s * im_k;
312  *ptr2 = re_s * im_k + im_s * re_k;
313  }
314 
315  // Compute the backward FFT
316  // Carefull, The backward FFT does not preserve the output
317  fftw_execute(ws.p_back);
318  // Scale the transform
319  for (ptr = ws.dst_fft, ptr_end = ws.dst_fft + ws.w_fftw * ws.h_fftw; ptr != ptr_end; ++ptr)
320  *ptr /= double(ws.h_fftw * ws.w_fftw);
321 }
322 
323 /* ************************************************************************* */
324 // find a number closest to the given one, which can be factorised according
325 // to fftw3 favorite factorisation
326 /* ************************************************************************* */
327 
329 {
330  if (is_optimal(n)) {
331  return n;
332  } else {
333  int j = n + 1;
334  while (!is_optimal(j))
335  ++j;
336  return j;
337  }
338 }
339 
340 /* ************************************************************************* */
341 // if a number can be factorised using only favorite fftw3 factors
342 /* ************************************************************************* */
344 {
345  if (n == 1)
346  return false;
347  size_t ntest = n;
348  for (size_t i = 0; i < m_implemented_factors.size(); i++) {
349  while ((ntest % m_implemented_factors[i]) == 0) {
350  ntest = ntest / m_implemented_factors[i];
351  }
352  }
353  return ntest == 1;
354 }
Defines class Math::Convolve.
double * out_src
result of Fourier transformation of source
Definition: Convolve.h:104
fftw_plan p_back
Definition: Convolve.h:115
double * in_src
adjusted input 'source' array
Definition: Convolve.h:102
double * in_kernel
adjusted input 'kernel' array
Definition: Convolve.h:106
double * dst_fft
result of product of FFT(source) and FFT(kernel)
Definition: Convolve.h:110
fftw_plan p_forw_src
Definition: Convolve.h:113
fftw_plan p_forw_kernel
Definition: Convolve.h:114
double * out_kernel
result of Fourier transformation of kernel
Definition: Convolve.h:108
EConvolutionMode m_mode
convolution mode
Definition: Convolve.h:121
Workspace ws
input and output data for fftw3
Definition: Convolve.h:119
std::vector< double1d_t > double2d_t
definition of 2d vector of double
Definition: Convolve.h:44
std::vector< size_t > m_implemented_factors
Definition: Convolve.h:122
std::vector< double > double1d_t
definition of 1d vector of double
Definition: Convolve.h:41
Convolve()
Definition: Convolve.cpp:20
@ FFTW_LINEAR_FULL
Definition: Convolve.h:51
@ FFTW_CIRCULAR_SAME_SHIFTED
Definition: Convolve.h:56
@ FFTW_LINEAR_VALID
Definition: Convolve.h:54
@ FFTW_UNDEFINED
Definition: Convolve.h:57
@ FFTW_LINEAR_SAME_UNPADDED
Definition: Convolve.h:52
@ FFTW_CIRCULAR_SAME
Definition: Convolve.h:55
@ FFTW_LINEAR_SAME
Definition: Convolve.h:53
void init(int h_src, int w_src, int h_kernel, int w_kernel)
prepare arrays for 2D convolution of given vectors
Definition: Convolve.cpp:155
void setMode(EConvolutionMode mode)
Sets convolution mode.
Definition: Convolve.h:70
void fftconvolve(const double1d_t &source, const double1d_t &kernel, double1d_t &result)
convolution in 1D
Definition: Convolve.cpp:138
bool is_optimal(int n)
if a number can be factorised using only favorite fftw3 factors
Definition: Convolve.cpp:343
void fftw_circular_convolution(const double2d_t &source, const double2d_t &kernel)
compute circual convolution of source and kernel using fast Fourier transformation
Definition: Convolve.cpp:273
int find_closest_factor(int n)
find closest number X>n that can be factorised according to fftw3 favorite factorisation
Definition: Convolve.cpp:328