Triumvirate C++ API 0.5.0
Three-point clustering measurements in large-scale structure analyses.
Loading...
Searching...
No Matches
fftlog.cpp
Go to the documentation of this file.
1// Copyright (C) [GPLv3 Licence]
2//
3// This file is part of the Triumvirate program. See the COPYRIGHT
4// and LICENCE files at the top-level directory of this distribution
5// for details of copyright and licensing.
6//
7// This program is free software: you can redistribute it and/or modify it
8// under the terms of the GNU General Public License as published by
9// the Free Software Foundation, either version 3 of the License, or
10// (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15// See the GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program. If not, see <https://www.gnu.org/licenses/>.
19
27#include "fftlog.hpp"
28
29namespace trva = trv::array;
30namespace trvm = trv::maths;
31namespace trvs = trv::sys;
32
33namespace trv {
34
35namespace maths {
36
37HankelTransform::HankelTransform(double mu, double q, bool threaded) {
38 // Check transform parameters.
39 if (
40 ((mu + 1. + q) <= 0. && std::floor(mu + 1. + q) == (mu + 1. + q)) ||
41 ((mu + 1. - q) <= 0. && std::floor(mu + 1. - q) == (mu + 1. - q))
42 ) {
44 "Invalid values of order `mu` and bias `q`: "
45 "negative poles in the gamma function."
46 );
47 }
48
49 this->order = mu;
50 this->bias = q;
51
52 this->threaded = threaded;
53
54 // Initialise FFTW plans.
55#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
56 if (this->threaded) {
57 fftw_init_threads();
58 fftw_plan_with_nthreads(omp_get_max_threads());
59 }
60#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
61}
62
64 this->reset();
65
66 // Do NOT call `fftw_cleanup` in reusable code here
67 // as it may cause memory leaks.
68// #if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
69// if (this->threaded) {
70// fftw_cleanup_threads();
71// } else {
72// fftw_cleanup();
73// }
74// #else // !TRV_USE_OMP || !TRV_USE_FFTWOMP
75// fftw_cleanup();
76// #endif // TRV_USE_OMP && TRV_USE_FFTWOMP
77}
78
79void HankelTransform::reset() {
80 if (this->plan_init) {
81 fftw_destroy_plan(this->pre_plan);
82 fftw_destroy_plan(this->post_plan);
83 this->plan_init = false;
84 }
85 if (this->pre_buffer != nullptr) {
86 fftw_free(this->pre_buffer); this->pre_buffer = nullptr;
87 }
88 if (this->post_buffer != nullptr) {
89 fftw_free(this->post_buffer); this->post_buffer = nullptr;
90 }
91}
92
94 std::vector<double> sample_pts, double kr_c, bool lowring,
95 trva::ExtrapOption extrap, double extrap_exp
96) {
97 // Initialise pre-sample points.
98 if (sample_pts.size() < 2) {
100 "The number of sample points must be at least 2."
101 );
102 }
103 if (trva::check_1d_array(sample_pts, false, true, false) != 0) {
105 "The sample points are not logarithmically spaced."
106 );
107 }
108
109 this->pre_sampts = sample_pts;
110 this->nsamp = sample_pts.size();
111 this->logres =
112 std::log(sample_pts.back() / sample_pts.front()) / (this->nsamp - 1);
113
114 this->extrap = extrap;
115 if (this->extrap != trva::ExtrapOption::NONE) {
116 if (this->nsamp % 2 != 0) {
118 "The number of sample points must be even for extrapolation."
119 );
120 }
121 this->nsamp_trans = std::pow(
122 2, std::ceil(std::log2(extrap_exp * this->nsamp))
123 );
124 if (this->nsamp_trans < this->nsamp) {
126 "The sample size expansion factor results in a shrunken sample size."
127 );
128 }
129
130 this->n_ext = (this->nsamp_trans - this->nsamp) / 2;
132 this->pre_sampts, this->n_ext, this->pre_sampts_extrap
133 );
134 } else {
135 this->nsamp_trans = this->nsamp;
136 this->n_ext = 0;
137 }
138
139 // Initialise the transform.
140 if (lowring) {
141 this->pivot = this->calc_lowring_pivot(this->logres, kr_c);
142 } else {
143 if (kr_c <= 0.) {
144 throw trvs::InvalidParameterError("Pivot value must be positive.");
145 }
146 this->pivot = kr_c;
147 }
148
149 this->kernel = this->compute_kernel_coeff();
150
151 // Initialise post-sample points.
152 // Note the end-point of the periodic interval is not included in
153 // the sample points, thus the shift in the product from the pivot
154 // by one sample point.
155 double kr_aprod = this->pivot * std::exp(- this->logres);
156
157 this->post_sampts.resize(this->nsamp);
158 for (int j = 0; j < this->nsamp; j++) {
159 this->post_sampts[j] = kr_aprod / this->pre_sampts[this->nsamp - j - 1];
160 }
161
162 if (this->extrap != trva::ExtrapOption::NONE) {
163 this->post_sampts_extrap.resize(this->nsamp_trans);
164 for (int j = 0; j < this->nsamp_trans; j++) {
165 this->post_sampts_extrap[j] =
166 kr_aprod / this->pre_sampts[this->nsamp_trans - j - 1];
167 }
168 }
169
170 // ---->
171 // // Alternative to the for-loop block above, note that k_c and r_c are
172 // // both exp(L/2) times `k0` and `r0`.
173 // double kr_0 = kr_aprod * std::exp(- this->nsamp * this->logres);
174 // double r0 = this->pre_sampts[0];
175 // double k0 = kr_0 / r0;
176 // ...
177 // ----<
178
179 // Initialise FFTW plans.
180 this->reset();
181
182 this->pre_buffer = fftw_alloc_complex(this->nsamp_trans);
183 this->pre_plan = fftw_plan_dft_1d(
184 this->nsamp_trans, this->pre_buffer, this->pre_buffer,
185 FFTW_FORWARD, FFTW_ESTIMATE
186 );
187
188 this->post_buffer = fftw_alloc_complex(this->nsamp_trans);
189 this->post_plan = fftw_plan_dft_1d(
190 this->nsamp_trans, this->post_buffer, this->post_buffer,
191 FFTW_FORWARD, FFTW_ESTIMATE
192 );
193
194 this->plan_init = true;
195}
196
198 std::vector<double> sample_pts, double kr_c, bool lowring,
199 int extrap, double extrap_exp
200) {
201 this->initialise(
202 sample_pts, kr_c, lowring, trva::ExtrapOption(extrap), extrap_exp
203 );
204}
205
206double HankelTransform::calc_lowring_pivot(double delta, double kr_c) {
207 // STYLE: Standard naming convention is not followed below.
208 double mu = this->order;
209 double q = this->bias;
210 double dL = delta;
211
212 double x_p = (mu + 1. + q)/2.;
213 double x_m = (mu + 1. - q)/2.;
214 double y = M_PI / (2.*dL);
215
216 if (kr_c > 0.) {
217 // Note that no minus sign is involved in the phase by
218 // complex conjugation of the gamma function.
219 double lnr_p, lnr_m, phi_p, phi_m;
220 trvm::get_lngamma_parts(x_p, y, lnr_p, phi_p);
221 trvm::get_lngamma_parts(x_m, y, lnr_m, phi_m);
222
223 double argphase = std::log(2./kr_c) / dL + (phi_p + phi_m) / M_PI;
224
225 kr_c *= std::exp(dL * (argphase - std::round(argphase)));
226 } else {
227 // Alternatively, simply set the value as follows.
228 std::complex<double> gamma_lnratio = eval_gamma_lnratio(mu, q + 2*y*M_I);
229
230 double lnkr_c = std::log(2) + gamma_lnratio.imag() / (2*y);
231
232 kr_c = std::exp(lnkr_c - std::floor(lnkr_c / dL) * dL);
233 }
234
235 return kr_c;
236}
237
238std::vector< std::complex<double> > HankelTransform::compute_kernel_coeff() {
239 // STYLE: Standard naming convention is not followed below.
240 double mu = this->order;
241 double q = this->bias;
242 int N_trans = this->nsamp_trans;
243 double dL = this->logres;
244 double kr_c = this->pivot;
245
246 if (N_trans <= 0 || dL <= 0. || kr_c <= 0.) {
247 throw std::runtime_error(
248 "This instance of trv::maths::HankelTransform has not been "
249 "initialised with `initialise`."
250 );
251 }
252
253 double x_p = (mu + 1. + q)/2.;
254 double x_m = (mu + 1. - q)/2.;
255 double x_eq = (mu + 1.)/2.;
256
257 double y = M_PI / (N_trans * dL);
258
259 double t = -2. * y * std::log(kr_c/2.);
260
261 // Note that no minus sign is involved in the phase by
262 // complex conjugation of the gamma function.
263 std::vector< std::complex<double> > u(N_trans);
264 if (q == 0.) {
265 for (int m = 0; m <= N_trans/2; m++) {
266 double lnr_, phi_eq;
267 trvm::get_lngamma_parts(x_eq, m*y, lnr_, phi_eq);
268
269 u[m] = trvm::eval_complex_in_polar(1., m*t + 2*phi_eq);
270 }
271 } else {
272 double qln2 = q * std::log(2.);
273 for (int m = 0; m <= N_trans/2; m++) {
274 double lnr_p, phi_p;
275 double lnr_m, phi_m;
276 trvm::get_lngamma_parts(x_p, m*y, lnr_p, phi_p);
277 trvm::get_lngamma_parts(x_m, m*y, lnr_m, phi_m);
278
280 std::exp(qln2 + lnr_p - lnr_m), m*t + phi_p + phi_m
281 );
282 }
283 }
284
285 for (int m = N_trans/2 + 1; m < N_trans; m++) {
286 u[m] = conj(u[N_trans - m]);
287 }
288
289 if (N_trans % 2 == 0) {
290 // Make the mid-point real by log-periodicity.
291 u[N_trans/2] = u[N_trans/2].real() + trvm::M_I * 0.;
292 }
293
294 return u;
295}
296
298 std::complex<double>* a, std::complex<double>* b
299) {
300 // STYLE: Standard naming convention is not followed below.
301 int N = this->nsamp;
302 int N_trans = this->nsamp_trans;
303
304 if (this->kernel.empty() || N <= 2 || N_trans <= 2) {
305 throw std::runtime_error(
306 "This instance of trv::maths::HankelTransform has not been "
307 "initialised with `initialise`."
308 );
309 }
310
311 // Perform any extrapolation required.
312 if (this->extrap == trva::ExtrapOption::NONE) {
313 for (int j = 0; j < N_trans; j++) {
314 this->pre_buffer[j][0] = (a[j]).real();
315 this->pre_buffer[j][1] = (a[j]).imag();
316 this->post_buffer[j][0] = 0.;
317 this->post_buffer[j][1] = 0.;
318 }
319 } else {
320 // Assume reality with extrapolation.
321 std::vector<double> a_vec(N);
322 for (int j = 0; j < N; j++) {
323 a_vec[j] = a[j].real();
324 }
325
326 std::vector<double> a_trans_vec(N_trans);
327 switch (this->extrap) {
328 case trva::ExtrapOption::NONE:
329 break;
330 case trva::ExtrapOption::ZERO:
332 a_vec, this->n_ext, 0., 0., a_trans_vec
333 );
334 break;
335 case trva::ExtrapOption::PAD:
337 a_vec, this->n_ext, a_vec.front(), a_vec.back(), a_trans_vec
338 );
339 break;
340 case trva::ExtrapOption::LIN:
341 trva::extrap_lin(a_vec, this->n_ext, a_trans_vec);
342 break;
343 case trva::ExtrapOption::LOGLIN:
344 trva::extrap_loglin(a_vec, this->n_ext, a_trans_vec);
345 break;
346 default:
347 throw trvs::InvalidParameterError("Unsupported extrapolation option.");
348 }
349
350 for (int j = 0; j < N_trans; j++) {
351 this->pre_buffer[j][0] = a_trans_vec[j];
352 this->pre_buffer[j][1] = 0.;
353 this->post_buffer[j][0] = 0.;
354 this->post_buffer[j][1] = 0.;
355 }
356 }
357
358 // Compute the convolution b = a * u using FFT.
359 fftw_execute(this->pre_plan);
360
361 for (int m = 0; m < N_trans; m++) {
362 // Divide by `N` to normalise the inverse DFT.
363 std::complex<double> a_(this->pre_buffer[m][0], this->pre_buffer[m][1]);
364 std::complex<double> b_ = a_ * this->kernel[m] / double(N_trans);
365 this->post_buffer[m][0] = b_.real();
366 this->post_buffer[m][1] = b_.imag();
367 }
368
369 fftw_execute(this->post_plan);
370
371 // Trim any extrapolation.
372 for (int j = 0; j < N; j++) {
373 b[j] = std::complex<double>(
374 this->post_buffer[j + this->n_ext][0],
375 this->post_buffer[j + this->n_ext][1]
376 );
377 }
378
379 // ---->
380 // // Reverse and shift the array `b_trans` when inverse FFT is used above
381 // // instead of FFT, and trim any extrapolation.
382 // for (int m = 0; m < N_trans/2; m++) {
383 // double b_real_ = this->post_buffer[m][0];
384 // double b_imag_ = this->post_buffer[m][1];
385
386 // this->post_buffer[m][0] = this->post_buffer[N_trans - m - 1][0];
387 // this->post_buffer[m][1] = this->post_buffer[N_trans - m - 1][1];
388
389 // this->post_buffer[N_trans - m - 1][0] = b_real_;
390 // this->post_buffer[N_trans - m - 1][1] = b_imag_;
391 // }
392 // for (int j = 0; j < N; j++) {
393 // int j_ = (j + N_trans - 1) % N_trans + this->n_ext;
394 // b[j] = std::complex<double>(
395 // this->post_buffer[j_][0],
396 // this->post_buffer[j_][1]
397 // );
398 // }
399 // ----<
400}
401
403 int ell, int n, bool threaded
404) : HankelTransform(ell + 1./2, double(n), threaded) {
405 this->degree = ell;
406}
407
409 std::vector<double> sample_pts, double kr_c, bool lowring,
410 trva::ExtrapOption extrap, double extrap_exp
411) {
412 HankelTransform::initialise(sample_pts, kr_c, lowring, extrap, extrap_exp);
413}
414
416 std::vector<double> sample_pts, double kr_c, bool lowring,
417 int extrap, double extrap_exp
418) {
419 HankelTransform::initialise(sample_pts, kr_c, lowring, extrap, extrap_exp);
420}
421
423 std::vector< std::complex<double> >& a,
424 std::vector< std::complex<double> >& b
425) {
426 // STYLE: Standard naming convention is not followed below.
427 int N = this->nsamp;
428
429 if (int(a.size()) != N) {
431 "The size of array `a` must be equal to the number of samples."
432 );
433 }
434 if (int(a.size()) != N) {
435 b.resize(N);
436 }
437
438 std::complex<double> A[N];
439 std::complex<double> B[N];
440
441 for (int j = 0; j < N; j++) {
442 A[j] = std::pow(this->pre_sampts[j], 3./2) * a[j];
443 }
444
446
447 for (int j = 0; j < N; j++) {
448 b[j] = std::pow(2*M_PI / this->post_sampts[j], 3./2) * B[j];
449 }
450}
451
453 int dir,
454 std::vector< std::complex<double> >& pre_samples,
455 std::vector< std::complex<double> >& post_samples
456) {
457 if (abs(dir) != 1) {
459 "The transform direction must be either +1 (forward) or -1 (backward).\n"
460 );
461 }
462
463 double pifactor = (dir == -1) ? std::pow(2*M_PI, -3) : 1.;
464 std::complex<double> parity = std::pow(trvm::M_I, - dir * this->degree);
465 std::complex<double> prefactor = pifactor * parity;
466
467 this->biased_transform(pre_samples, post_samples);
468
469 for (int j = 0; j <this->nsamp; j++) {
470 post_samples[j] *= prefactor;
471 }
472}
473
474} // namespace trv::maths
475
476} // namespace trv
Perform the (forward biased) Hankel and associated transforms using the FFTLog algorithm.
Definition fftlog.hpp:53
HankelTransform(double mu, double q, bool threaded=true)
Construct the Hankel transform.
Definition fftlog.cpp:37
int nsamp
number of samples provided
Definition fftlog.hpp:57
std::vector< double > post_sampts
logarithmically linearly-spaced sample points post-transform
Definition fftlog.hpp:66
void biased_transform(std::complex< double > *a, std::complex< double > *b)
Perform the (forward biased) Hankel transform.
Definition fftlog.cpp:297
double order
order of the Hankel transform
Definition fftlog.hpp:55
void initialise(std::vector< double > sample_pts, double kr_c, bool lowring=true, trva::ExtrapOption extrap=trva::ExtrapOption::NONE, double extrap_exp=2.)
Initialise the Hankel transform.
Definition fftlog.cpp:93
double calc_lowring_pivot(double delta, double kr_c=1.)
Calculate a low-ringing FFTLog transform pivot value .
Definition fftlog.cpp:206
double bias
power-law bias index
Definition fftlog.hpp:56
int nsamp_trans
number of samples transformed
Definition fftlog.hpp:58
~HankelTransform()
Destruct the Hankel transform.
Definition fftlog.cpp:63
double pivot
pivot value
Definition fftlog.hpp:60
double logres
logarithmic interval sample spacing
Definition fftlog.hpp:59
std::vector< std::complex< double > > compute_kernel_coeff()
Compute the FFTLog transform kernel coefficients .
Definition fftlog.cpp:238
std::vector< double > pre_sampts
logarithmically linearly-spaced sample points pre-transform
Definition fftlog.hpp:63
void biased_transform(std::vector< std::complex< double > > &a, std::vector< std::complex< double > > &b)
Perform the (forward biased) spherical Bessel transform.
Definition fftlog.cpp:422
void initialise(std::vector< double > sample_pts, double kr_c, bool lowring=true, trva::ExtrapOption extrap=trva::ExtrapOption::NONE, double extrap_exp=2.)
Initialise the spherical Bessel transform.
Definition fftlog.cpp:408
void transform_cosmological_multipole(int dir, std::vector< std::complex< double > > &pre_samples, std::vector< std::complex< double > > &post_samples)
Transform csomological multipole samples.
Definition fftlog.cpp:452
int degree
degree of the spherical Bessel transform
Definition fftlog.hpp:235
SphericalBesselTransform(int ell, int n, bool threaded=true)
Construct the spherical Bessel transform.
Definition fftlog.cpp:402
Exception raised when parameters are invalid.
Definition monitor.hpp:432
FFTLog algorithm for Hankel and related transforms and its application to cosmological functions.
int check_1d_array(std::vector< double > &a, bool check_lin, bool check_loglin, bool check_sign)
Check the linearity or log-linearity of a 1-d array.
Definition arrayops.cpp:58
ExtrapOption
Extrapolation scheme.
Definition arrayops.hpp:83
void extrap_loglin(std::vector< double > &a, int N_ext, std::vector< double > &a_ext)
Extrapolate a 1-d array exponentially (i.e. log-linearly).
Definition arrayops.cpp:121
void extrap_lin(std::vector< double > &a, int N_ext, std::vector< double > &a_ext)
Extrapolate a 1-d array linearly.
Definition arrayops.cpp:92
void extrap_pad(std::vector< double > &a, int N_ext, double c_lower, double c_upper, std::vector< double > &a_ext)
Extrapolate a 1-d array by constant padding.
Definition arrayops.cpp:180
std::complex< double > eval_complex_in_polar(double r, double theta)
Evaluate a complex number in the polar form.
Definition maths.cpp:42
void get_lngamma_parts(double x, double y, double &lnr, double &theta)
Get the real and imaginary parts of the log-gamma function .
Definition maths.cpp:120
std::complex< double > eval_gamma_lnratio(double mu, std::complex< double > nu)
Evaluate the logarithm of the ratio of two gamma functions.
Definition maths.cpp:128
const std::complex< double > M_I
imaginary unit