Triumvirate C++ API 0.5.0.post1.dev301+g026f21751
Three-point clustering measurements in large-scale structure analyses.
Loading...
Searching...
No Matches
threept.cpp
Go to the documentation of this file.
1// Triumvirate: Three-Point Clustering Measurements in LSS
2//
3// Copyright (C) 2023 Mike S Wang & Naonori S Sugiyama [GPL-3.0-or-later]
4//
5// This file is part of the Triumvirate program. See the COPYRIGHT
6// and LICENCE files at the top-level directory of this distribution
7// for details of copyright and licensing.
8//
9// This program is free software: you can redistribute it and/or modify it
10// under the terms of the GNU General Public License as published by
11// the Free Software Foundation, either version 3 of the License, or
12// (at your option) any later version.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17// See the GNU General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License
20// along with this program. If not, see <https://www.gnu.org/licenses/>.
21
28
29#include "threept.hpp"
30
31namespace trvs = trv::sys;
32namespace trvm = trv::maths;
33
34namespace trv {
35
36// ***********************************************************************
37// Spherical orders
38// ***********************************************************************
39
41 const SphericalOrderTriplet& other
42) {
43 SphericalOrderTriplet sph_order_triplet = {
44 this->m1 + other.m1, this->m2 + other.m2, this->M + other.M
45 };
46 return sph_order_triplet;
47}
48
50 return (this->m1 == 0) && (this->m2 == 0) && (this->M == 0);
51}
52
54 return
55 (this->m1 + other.m1 == 0) &&
56 (this->m2 + other.m2 == 0) &&
57 (this->M + other.M == 0);
58}
59
60
61// ***********************************************************************
62// Coupling coefficients
63// ***********************************************************************
64
66 int ell1, int ell2, int ELL, int m1, int m2, int M
67) {
68 return double(2*ell1 + 1) * double(2*ell2 + 1) * double(2*ELL + 1)
69 * trvm::wigner_3j(ell1, ell2, ELL, 0, 0, 0)
70 * trvm::wigner_3j(ell1, ell2, ELL, m1, m2, M);
71}
72
74 double coupling_ = trvm::wigner_3j(
75 params.ell1, params.ell2, params.ELL, 0, 0, 0
76 );
77 if (std::fabs(coupling_) < trvm::eps_coupling) {
78 if (trvs::currTask == 0) {
79 trvs::logger.error(
80 "Specified three-point correlator multipole "
81 "vanishes identically owing to zero-valued Wigner 3-j symbol."
82 );
83 }
85 "Specified three-point correlator multipole "
86 "vanishes identically owing to zero-valued Wigner 3-j symbol."
87 );
88 }
89}
90
91
92// ***********************************************************************
93// Normalisation
94// ***********************************************************************
95
97 ParticleCatalogue& particles, double alpha
98) {
99 if (particles.pdata == nullptr) {
100 if (trvs::currTask == 0) {
101 trvs::logger.error("Particle data are uninitialised.");
102 }
103 throw trvs::InvalidDataError("Particle data are uninitialised.");
104 }
105
106 double norm = 0.; // I₃
107
108#ifdef TRV_USE_OMP
109#if defined(__GNUC__) && !defined(__clang__)
110#pragma omp parallel for simd reduction(+:norm)
111#else // !__GNUC__ || __clang__
112#pragma omp parallel for reduction(+:norm)
113#endif // __GNUC__ && !__clang__
114#endif // TRV_USE_OMP
115 for (int pid = 0; pid < particles.ntotal; pid++) {
116 norm += particles[pid].ws
117 * std::pow(particles[pid].nz, 2) * std::pow(particles[pid].wc, 3);
118 }
119
120 if (norm == 0.) {
121 if (trvs::currTask == 0) {
122 trvs::logger.error(
123 "Particle 'nz' values appear to be all zeros. "
124 "Check the input catalogue contains valid 'nz' field."
125 );
126 }
128 "Particle 'nz' values appear to be all zeros. "
129 "Check the input catalogue contains valid 'nz' field."
130 );
131 }
132
133 double norm_factor = 1. / (alpha * norm); // 1/I₃
134
135 return norm_factor;
136}
137
139 ParticleCatalogue& particles, trv::ParameterSet& params, double alpha
140) {
141 MeshField catalogue_mesh(params, false, "`catalogue_mesh`");
142
143 double norm_factor =
144 catalogue_mesh.calc_grid_based_powlaw_norm(particles, 3);
145
146 norm_factor /= std::pow(alpha, 3);
147
148 return norm_factor;
149}
150
151
152// ***********************************************************************
153// Shot noise
154// ***********************************************************************
155
157 ParticleCatalogue& particles_data, ParticleCatalogue& particles_rand,
158 LineOfSight* los_data, LineOfSight* los_rand,
159 double alpha, int ell, int m
160) {
161 double sn_data_real = 0., sn_data_imag = 0.;
162
163#ifdef TRV_USE_OMP
164#pragma omp parallel for reduction(+:sn_data_real, sn_data_imag)
165#endif
166 for (int pid = 0; pid < particles_data.ntotal; pid++) {
167 double los_[3] = {
168 los_data[pid].pos[0], los_data[pid].pos[1], los_data[pid].pos[2]
169 };
170
171 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
173
174 std::complex<double> sn_part = ylm * std::pow(particles_data[pid].w, 3);
175 double sn_part_real = sn_part.real();
176 double sn_part_imag = sn_part.imag();
177
178 sn_data_real += sn_part_real;
179 sn_data_imag += sn_part_imag;
180 }
181
182 std::complex<double> sn_data(sn_data_real, sn_data_imag);
183
184 double sn_rand_real = 0., sn_rand_imag = 0.;
185
186#ifdef TRV_USE_OMP
187#pragma omp parallel for reduction(+:sn_rand_real, sn_rand_imag)
188#endif
189 for (int pid = 0; pid < particles_rand.ntotal; pid++) {
190 double los_[3] = {
191 los_rand[pid].pos[0], los_rand[pid].pos[1], los_rand[pid].pos[2]
192 };
193
194 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
196
197 std::complex<double> sn_part = ylm * std::pow(particles_rand[pid].w, 3);
198 double sn_part_real = sn_part.real();
199 double sn_part_imag = sn_part.imag();
200
201 sn_rand_real += sn_part_real;
202 sn_rand_imag += sn_part_imag;
203 }
204
205 std::complex<double> sn_rand(sn_rand_real, sn_rand_imag);
206
207 return sn_data + std::pow(alpha, 3) * sn_rand;
208}
209
211 ParticleCatalogue& particles, LineOfSight* los,
212 double alpha, int ell, int m
213) {
214 double sn_real = 0., sn_imag = 0.;
215
216#ifdef TRV_USE_OMP
217#pragma omp parallel for reduction(+:sn_real, sn_imag)
218#endif
219 for (int pid = 0; pid < particles.ntotal; pid++) {
220 double los_[3] = {los[pid].pos[0], los[pid].pos[1], los[pid].pos[2]};
221
222 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
224
225 std::complex<double> sn_part = ylm * std::pow(particles[pid].w, 3);
226 double sn_part_real = sn_part.real();
227 double sn_part_imag = sn_part.imag();
228
229 sn_real += sn_part_real;
230 sn_imag += sn_part_imag;
231 }
232
233 std::complex<double> sn(sn_real, sn_imag);
234
235 return std::pow(alpha, 3) * sn;
236}
237
238
239// ***********************************************************************
240// Full statistics
241// ***********************************************************************
242
243// STYLE: Standard naming convention is not always followed for
244// intermediary quantities in the functions below.
245
246// Hereafter 'the Paper' refers to Sugiyama et al. (2019) [1803.02132].
247
249 ParticleCatalogue& catalogue_data, ParticleCatalogue& catalogue_rand,
250 LineOfSight* los_data, LineOfSight* los_rand,
251 trv::ParameterSet& params, trv::Binning& kbinning,
252 double norm_factor
253) {
254 trvs::logger.reset_level(params.verbose);
255
256 if (trvs::currTask == 0) {
257 trvs::logger.stat(
258 "Computing bispectrum from paired survey-type catalogues..."
259 );
260 }
261
262 // ---------------------------------------------------------------------
263 // Set-up
264 // ---------------------------------------------------------------------
265
266 // Set up/check input.
268
269 double alpha = catalogue_data.wstotal / catalogue_rand.wstotal;
270
271 std::complex<double> factor_phase =
272 std::pow(trvm::M_I, params.ell1 + params.ell2);
273
274 // Set up output.
275 int dv_dim = 0; // data vector dimension
276 if (params.shape == "diag" || params.shape == "row" ) {
277 dv_dim = kbinning.num_bins;
278 } else
279 if (params.shape == "off-diag") {
280 dv_dim = kbinning.num_bins - std::abs(params.idx_bin);
281 } else
282 if (params.shape == "full") {
283 dv_dim = kbinning.num_bins * kbinning.num_bins;
284 } else
285 if (params.shape == "triu") {
286 dv_dim = kbinning.num_bins * (kbinning.num_bins + 1) / 2;
287 } else {
288 if (trvs::currTask == 0) {
289 trvs::logger.error(
290 "Three-point statistic form is not recognised: `form` = '%s'.",
291 params.form.c_str()
292 );
293 }
295 "Three-point statistic form is not recognised: `form` = '%s'.",
296 params.form.c_str()
297 );
298 }
299
300 int* nmodes1_dv = new int[dv_dim]();
301 int* nmodes2_dv = new int[dv_dim]();
302 double* k1bin_dv = new double[dv_dim]();
303 double* k2bin_dv = new double[dv_dim]();
304 double* k1eff_dv = new double[dv_dim]();
305 double* k2eff_dv = new double[dv_dim]();
306 std::complex<double>* bk_dv = new std::complex<double>[dv_dim];
307 std::complex<double>* sn_dv = new std::complex<double>[dv_dim];
308
309 // ---------------------------------------------------------------------
310 // Measurement
311 // ---------------------------------------------------------------------
312
313#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
314 if (!trvs::is_gpu_enabled()) {
315 fftw_init_threads();
316 }
317#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
318
319 // Compute common field quantities.
320 MeshField dn_00(params, true, "`dn_00`"); // δn_00(k)
322 catalogue_data, catalogue_rand, los_data, los_rand, alpha, 0, 0
323 );
324 dn_00.fourier_transform();
325
326 MeshField& dn_00_for_sn = dn_00; // δn_00(k) (for shot noise)
327
328 double vol_cell = dn_00.vol_cell;
329
330 MeshField N_00(params, true, "`N_00`"); // N_00(k)
332 catalogue_data, catalogue_rand, los_data, los_rand, alpha, 0, 0
333 );
334 N_00.fourier_transform();
335
336 trvm::SphericalBesselCalculator sj_a(params.ell1); // j_l_a
337 trvm::SphericalBesselCalculator sj_b(params.ell2); // j_l_b
338
339 FieldStats stats_sn(params);
340
341 // Initialise reduced-spherical-harmonic weights on mesh grids.
342 std::vector< std::complex<double> > ylm_k_a(params.nmesh);
343 std::vector< std::complex<double> > ylm_k_b(params.nmesh);
344 std::vector< std::complex<double> > ylm_r_a(params.nmesh);
345 std::vector< std::complex<double> > ylm_r_b(params.nmesh);
347 trvs::count_grid += 4;
352
353 // Compute bispectrum terms including shot noise.
354 std::vector<trv::SphericalOrderTriplet> sph_orders_computed;
355 int count_terms = 0;
356 for (int m1_ = - params.ell1; m1_ <= params.ell1; m1_++) {
357 for (int m2_ = - params.ell2; m2_ <= params.ell2; m2_++) {
358 // Check for if all Wigner-3j symbols are zero.
359 std::string flag_vanishing = "true";
360 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
361 double coupling = trv::calc_coupling_coeff_3pt(
362 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
363 );
364 if (std::fabs(coupling) > trvm::eps_coupling) {
365 flag_vanishing = "false";
366 break;
367 }
368 }
369 if (flag_vanishing == "true") {continue;}
370
373 params.ell1, m1_, params.boxsize, params.ngrid, ylm_k_a
374 );
377 params.ell2, m2_, params.boxsize, params.ngrid, ylm_k_b
378 );
381 params.ell1, m1_, params.boxsize, params.ngrid, ylm_r_a
382 );
385 params.ell2, m2_, params.boxsize, params.ngrid, ylm_r_b
386 );
387
388 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
389 // Check if the current spherical order triplet is redundant.
390 SphericalOrderTriplet sph_order_current = {m1_, m2_, M_};
391
392 std::string flag_redundant = "false";
393 for (SphericalOrderTriplet sph_order : sph_orders_computed) {
394 if (sph_order_current.is_inverse(sph_order)) {
395 flag_redundant = "true";
396 }
397 }
398 if (flag_redundant == "true") {continue;}
399
400 // Calculate the coupling coefficient.
401 double coupling = trv::calc_coupling_coeff_3pt(
402 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
403 ); // Wigner 3-j's
404 if (std::fabs(coupling) < trvm::eps_coupling) {continue;}
405
406 // The factors consist of products of
407 // ```
408 // {std::pow(-1., params.ell1 + params.ell2 + params.ELL),
409 // 1},
410 // {std::pow(-1., m1_ + m2_ + M_),
411 // std::pow(-1., 2*M_)} ≡ 1,
412 // {std::pow(-1., params.ell1 + params.ell2),
413 // std::pow(-1., params.ELL)} ≡ std::pow(-1., params.ELL)}
414 // ```
415 // which, by parity symmetry & Wigner 3-j properties, simplify to:
416 double factor_mirror = sph_order_current.is_zeros() ?
417 0. : std::pow(-1., params.ELL);
418 double factor_mirror_w3j = sph_order_current.is_zeros() ?
419 0. : 1.; // std::pow(-1., params.ell1 + params.ell2 + params.ELL);
420
421 // Set up FFT counter and progress bar.
422 int progbar_task_count = 4 + 3 * dv_dim;
423 std::stringstream progbar_name_ss;
424 progbar_name_ss
425 << "component orders (" << m1_ << ", " << m2_ << ", " << M_ << ")";
426 trvs::ProgressBar progbar(progbar_task_count, progbar_name_ss.str());
427
428 std::vector<float> progbar_nodes;
429 if (params.progbar != "false" && params.progbar != "true") {
430 progbar_nodes = trvs::set_nodes_by_str(params.progbar);
431 if (progbar_nodes.size() > 0) {
432 progbar.set_nodes(progbar_nodes);
433 }
434 }
435
436 int count_fft_init = trvs::count_fft + trvs::count_ifft;
437 auto update_progbar = [&]() {
438 if (params.progbar != "false") {
439 if (trvs::currTask == 0) {
440 progbar.update(
441 trvs::count_fft + trvs::count_ifft - count_fft_init
442 );
443 }
444 }
445 };
446
447 // ·······························································
448 // Raw bispectrum
449 // ·······························································
450
451 // Compute bispectrum components in eqs. (41) & (42) in the Paper.
452 MeshField G_LM(params, true, "`G_LM`"); // G_LM
454 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
455 params.ELL, M_
456 );
457 G_LM.fourier_transform();
460 update_progbar();
461
462 MeshField F_lm_a(params, true, "`F_lm_a`"); // F_lm_a
463 MeshField F_lm_b(params, true, "`F_lm_b`"); // F_lm_b
464
465 if (params.shape == "diag") {
466 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
467 int ibin = idx_dv;
468
469 double k_lower = kbinning.bin_edges[ibin];
470 double k_upper = kbinning.bin_edges[ibin + 1];
471
472 double k_eff_a_, k_eff_b_;
473 int nmodes_a_, nmodes_b_;
474
476 dn_00, ylm_k_a, k_lower, k_upper, k_eff_a_, nmodes_a_
477 );
479 dn_00, ylm_k_b, k_lower, k_upper, k_eff_b_, nmodes_b_
480 );
481 update_progbar();
482
483 if (count_terms == 0) {
484 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin];
485 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin];
486 k1eff_dv[idx_dv] = k_eff_a_;
487 k2eff_dv[idx_dv] = k_eff_b_;
488 nmodes1_dv[idx_dv] = nmodes_a_;
489 nmodes2_dv[idx_dv] = nmodes_b_;
490 }
491
492 // B_{l₁ l₂ L}^{m₁ m₂ M}
493 double bk_comp_real = 0., bk_comp_imag = 0.;
494
495#ifdef TRV_USE_OMP
496#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
497#endif // TRV_USE_OMP
498 for (long long gid = 0; gid < params.nmesh; gid++) {
499 std::complex<double> F_lm_a_gridpt(
500 F_lm_a[gid][0], F_lm_a[gid][1]
501 );
502 std::complex<double> F_lm_b_gridpt(
503 F_lm_b[gid][0], F_lm_b[gid][1]
504 );
505 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
506 std::complex<double> bk_gridpt =
507 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
508
509 bk_comp_real += bk_gridpt.real();
510 bk_comp_imag += bk_gridpt.imag();
511 }
512
513 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
514
515 bk_dv[idx_dv] += coupling * vol_cell * (
516 bk_component + factor_mirror * std::conj(bk_component)
517 );
518 }
519 }
520
521 if (params.shape == "off-diag") {
522 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
523 int ibin_row, ibin_col;
524 if (params.idx_bin >= 0) {
525 ibin_row = idx_dv;
526 ibin_col = idx_dv + std::abs(params.idx_bin);
527 } else {
528 ibin_row = idx_dv + std::abs(params.idx_bin);
529 ibin_col = idx_dv;
530 }
531
532 double k_lower_a = kbinning.bin_edges[ibin_row];
533 double k_upper_a = kbinning.bin_edges[ibin_row + 1];
534 double k_lower_b = kbinning.bin_edges[ibin_col];
535 double k_upper_b = kbinning.bin_edges[ibin_col + 1];
536
537 double k_eff_a_, k_eff_b_;
538 int nmodes_a_, nmodes_b_;
539
541 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
542 );
544 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
545 );
546 update_progbar();
547
548 if (count_terms == 0) {
549 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin_row];
550 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin_col];
551 k1eff_dv[idx_dv] = k_eff_a_;
552 k2eff_dv[idx_dv] = k_eff_b_;
553 nmodes1_dv[idx_dv] = nmodes_a_;
554 nmodes2_dv[idx_dv] = nmodes_b_;
555 }
556
557 // B_{l₁ l₂ L}^{m₁ m₂ M}
558 double bk_comp_real = 0., bk_comp_imag = 0.;
559
560#ifdef TRV_USE_OMP
561#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
562#endif // TRV_USE_OMP
563 for (long long gid = 0; gid < params.nmesh; gid++) {
564 std::complex<double> F_lm_a_gridpt(
565 F_lm_a[gid][0], F_lm_a[gid][1]
566 );
567 std::complex<double> F_lm_b_gridpt(
568 F_lm_b[gid][0], F_lm_b[gid][1]
569 );
570 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
571 std::complex<double> bk_gridpt =
572 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
573
574 bk_comp_real += bk_gridpt.real();
575 bk_comp_imag += bk_gridpt.imag();
576 }
577
578 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
579
580 bk_dv[idx_dv] += coupling * vol_cell * (
581 bk_component + factor_mirror * std::conj(bk_component)
582 );
583 }
584 }
585
586 if (params.shape == "row") {
587 int ibin_row = params.idx_bin;
588
589 double k_lower_a = kbinning.bin_edges[ibin_row];
590 double k_upper_a = kbinning.bin_edges[ibin_row + 1];
591
592 double k_eff_a_;
593 int nmodes_a_;
594
596 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
597 );
598
599 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
600 int ibin_col = idx_dv;
601
602 double k_lower_b = kbinning.bin_edges[ibin_col];
603 double k_upper_b = kbinning.bin_edges[ibin_col + 1];
604
605 double k_eff_b_;
606 int nmodes_b_;
607
609 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
610 );
611 update_progbar();
612
613 if (count_terms == 0) {
614 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin_row];
615 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin_col];
616 k1eff_dv[idx_dv] = k_eff_a_;
617 k2eff_dv[idx_dv] = k_eff_b_;
618 nmodes1_dv[idx_dv] = nmodes_a_;
619 nmodes2_dv[idx_dv] = nmodes_b_;
620 }
621
622 // B_{l₁ l₂ L}^{m₁ m₂ M}
623 double bk_comp_real = 0., bk_comp_imag = 0.;
624
625#ifdef TRV_USE_OMP
626#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
627#endif // TRV_USE_OMP
628 for (long long gid = 0; gid < params.nmesh; gid++) {
629 std::complex<double> F_lm_a_gridpt(
630 F_lm_a[gid][0], F_lm_a[gid][1]
631 );
632 std::complex<double> F_lm_b_gridpt(
633 F_lm_b[gid][0], F_lm_b[gid][1]
634 );
635 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
636 std::complex<double> bk_gridpt =
637 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
638
639 bk_comp_real += bk_gridpt.real();
640 bk_comp_imag += bk_gridpt.imag();
641 }
642
643 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
644
645 bk_dv[idx_dv] += coupling * vol_cell * (
646 bk_component + factor_mirror * std::conj(bk_component)
647 );
648 }
649 }
650
651 if (params.shape == "full") {
652 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
653 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
654 int idx_dv = idx_row * params.num_bins + idx_col;
655
656 double k_lower_a = kbinning.bin_edges[idx_row];
657 double k_upper_a = kbinning.bin_edges[idx_row + 1];
658 double k_lower_b = kbinning.bin_edges[idx_col];
659 double k_upper_b = kbinning.bin_edges[idx_col + 1];
660
661 double k_eff_a_, k_eff_b_;
662 int nmodes_a_, nmodes_b_;
663
665 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
666 );
668 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
669 );
670 update_progbar();
671
672 if (count_terms == 0) {
673 k1bin_dv[idx_dv] = kbinning.bin_centres[idx_row];
674 k2bin_dv[idx_dv] = kbinning.bin_centres[idx_col];
675 k1eff_dv[idx_dv] = k_eff_a_;
676 k2eff_dv[idx_dv] = k_eff_b_;
677 nmodes1_dv[idx_dv] = nmodes_a_;
678 nmodes2_dv[idx_dv] = nmodes_b_;
679 }
680
681 // B_{l₁ l₂ L}^{m₁ m₂ M}
682 double bk_comp_real = 0., bk_comp_imag = 0.;
683
684#ifdef TRV_USE_OMP
685#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
686#endif // TRV_USE_OMP
687 for (long long gid = 0; gid < params.nmesh; gid++) {
688 std::complex<double> F_lm_a_gridpt(
689 F_lm_a[gid][0], F_lm_a[gid][1]
690 );
691 std::complex<double> F_lm_b_gridpt(
692 F_lm_b[gid][0], F_lm_b[gid][1]
693 );
694 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
695 std::complex<double> bk_gridpt =
696 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
697
698 bk_comp_real += bk_gridpt.real();
699 bk_comp_imag += bk_gridpt.imag();
700 }
701
702 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
703
704 bk_dv[idx_dv] += coupling * vol_cell * (
705 bk_component + factor_mirror * std::conj(bk_component)
706 );
707 }
708 }
709 }
710
711 if (params.shape == "triu") {
712 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
713 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++) {
714 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
715 + (idx_col - idx_row);
716
717 double k_lower_a = kbinning.bin_edges[idx_row];
718 double k_upper_a = kbinning.bin_edges[idx_row + 1];
719 double k_lower_b = kbinning.bin_edges[idx_col];
720 double k_upper_b = kbinning.bin_edges[idx_col + 1];
721
722 double k_eff_a_, k_eff_b_;
723 int nmodes_a_, nmodes_b_;
724
726 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
727 );
729 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
730 );
731 update_progbar();
732
733 if (count_terms == 0) {
734 k1bin_dv[idx_dv] = kbinning.bin_centres[idx_row];
735 k2bin_dv[idx_dv] = kbinning.bin_centres[idx_col];
736 k1eff_dv[idx_dv] = k_eff_a_;
737 k2eff_dv[idx_dv] = k_eff_b_;
738 nmodes1_dv[idx_dv] = nmodes_a_;
739 nmodes2_dv[idx_dv] = nmodes_b_;
740 }
741
742 // B_{l₁ l₂ L}^{m₁ m₂ M}
743 double bk_comp_real = 0., bk_comp_imag = 0.;
744
745#ifdef TRV_USE_OMP
746#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
747#endif // TRV_USE_OMP
748 for (long long gid = 0; gid < params.nmesh; gid++) {
749 std::complex<double> F_lm_a_gridpt(
750 F_lm_a[gid][0], F_lm_a[gid][1]
751 );
752 std::complex<double> F_lm_b_gridpt(
753 F_lm_b[gid][0], F_lm_b[gid][1]
754 );
755 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
756 std::complex<double> bk_gridpt =
757 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
758
759 bk_comp_real += bk_gridpt.real();
760 bk_comp_imag += bk_gridpt.imag();
761 }
762
763 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
764
765 bk_dv[idx_dv] += coupling * vol_cell * (
766 bk_component + factor_mirror * std::conj(bk_component)
767 );
768 }
769 }
770 }
771
772 // ·······························································
773 // Shot noise
774 // ·······························································
775
776 // Compute shot noise components in eqs. (45) & (46) in the Paper.
777 MeshField dn_LM_for_sn(params, true, "`dn_LM_for_sn`"); // δn_LM(k)
778 // (for
779 // shot noise)
780 dn_LM_for_sn.compute_ylm_wgtd_field(
781 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
782 params.ELL, M_
783 );
784 dn_LM_for_sn.fourier_transform();
785
786 MeshField N_LM(params, true, "`N_LM`"); // N_LM(k)
788 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
789 params.ELL, M_
790 );
791 N_LM.fourier_transform();
792 update_progbar();
793
794 std::complex<double> Sbar_LM = calc_ylm_wgtd_shotnoise_amp_for_bispec(
795 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
796 params.ELL, M_
797 ); // \bar{S}_LM
798
799 if (params.ell1 == 0 && params.ell2 == 0) {
800 // When l₁ = l₂ = 0, the Wigner 3-j symbol enforces L = 0
801 // and the pre-factors involving degrees and orders become 1.
802 std::complex<double> S_ijk = coupling * Sbar_LM; // S|{i = j = k}
803 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
804 sn_dv[idx_dv] += S_ijk;
805 }
806 }
807
808 if (params.ell2 == 0) { // S|{i ≠ j = k}
809 // When l₂ = 0, the Wigner 3-j symbol enforces L = l₁.
811 dn_00_for_sn, N_LM, Sbar_LM, params.ell1, m1_, kbinning
812 );
813
814 if (params.shape == "diag") {
815 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
816 int ibin = idx_dv;
817 std::complex<double> S_i_jk = coupling * (
818 stats_sn.pk[ibin] - stats_sn.sn[ibin]
819 );
820 sn_dv[idx_dv] += S_i_jk + factor_mirror * std::conj(S_i_jk);
821 }
822 }
823
824 if (params.shape == "off-diag") {
825 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
826 int ibin_row;
827 if (params.idx_bin >= 0) {
828 ibin_row = idx_dv;
829 } else {
830 ibin_row = idx_dv + std::abs(params.idx_bin);
831 }
832 std::complex<double> S_i_jk = coupling * (
833 stats_sn.pk[ibin_row] - stats_sn.sn[ibin_row]
834 );
835 sn_dv[idx_dv] += S_i_jk + factor_mirror * std::conj(S_i_jk);
836 }
837 }
838
839 if (params.shape == "row") {
840 std::complex<double> S_i_jk_row_ = coupling * (
841 stats_sn.pk[params.idx_bin] - stats_sn.sn[params.idx_bin]
842 );
843 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
844 sn_dv[idx_dv] +=
845 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
846 }
847 }
848
849 if (params.shape == "full") {
850 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
851 std::complex<double> S_i_jk_row_ = coupling * (
852 stats_sn.pk[idx_row] - stats_sn.sn[idx_row]
853 );
854 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
855 int idx_dv = idx_row * params.num_bins + idx_col;
856 sn_dv[idx_dv] +=
857 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
858 }
859 }
860 }
861
862 if (params.shape == "triu") {
863 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
864 std::complex<double> S_i_jk_row_ = coupling * (
865 stats_sn.pk[idx_row] - stats_sn.sn[idx_row]
866 );
867 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++)
868 {
869 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
870 + (idx_col - idx_row);
871 sn_dv[idx_dv] +=
872 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
873 }
874 }
875 }
876 }
877
878 if (params.ell1 == 0) { // S|{j ≠ i = k}
879 // When l₁ = 0, the Wigner 3-j symbol enforces L = l₂.
881 dn_00_for_sn, N_LM, Sbar_LM, params.ell2, m2_, kbinning
882 );
883
884 if (params.shape == "diag") {
885 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
886 int ibin = idx_dv;
887 std::complex<double> S_ik_j_ki = coupling * (
888 stats_sn.pk[ibin] - stats_sn.sn[ibin]
889 );
890 sn_dv[idx_dv] +=
891 S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
892 }
893 }
894
895 if (params.shape == "off-diag") {
896 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
897 int ibin_col;
898 if (params.idx_bin >= 0) {
899 ibin_col = idx_dv + params.idx_bin;
900 } else {
901 ibin_col = idx_dv;
902 }
903 std::complex<double> S_ik_j_ki = coupling * (
904 stats_sn.pk[ibin_col] - stats_sn.sn[ibin_col]
905 );
906 sn_dv[idx_dv] +=
907 S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
908 }
909 }
910
911 if (params.shape == "row") {
912 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
913 int ibin_col = idx_dv;
914 std::complex<double> S_ik_j_ki = coupling * (
915 stats_sn.pk[ibin_col] - stats_sn.sn[ibin_col]
916 );
917 sn_dv[idx_dv] +=
918 S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
919 }
920 }
921
922 if (params.shape == "full") {
923 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
924 std::complex<double> S_ik_j_ki_col_ = coupling * (
925 stats_sn.pk[idx_col] - stats_sn.sn[idx_col]
926 );
927 for (int idx_row = 0; idx_row <= params.num_bins; idx_row++) {
928 int idx_dv = idx_row * params.num_bins + idx_col;
929 sn_dv[idx_dv] +=
930 S_ik_j_ki_col_ + factor_mirror * std::conj(S_ik_j_ki_col_);
931 }
932 }
933 }
934
935 if (params.shape == "triu") {
936 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
937 std::complex<double> S_ik_j_ki_col_ = coupling * (
938 stats_sn.pk[idx_col] - stats_sn.sn[idx_col]
939 );
940 for (int idx_row = 0; idx_row <= idx_col; idx_row++) {
941 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
942 + (idx_col - idx_row);
943 sn_dv[idx_dv] +=
944 S_ik_j_ki_col_ + factor_mirror * std::conj(S_ik_j_ki_col_);
945 }
946 }
947 }
948 }
949
950 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
951 double k_a = k1eff_dv[idx_dv];
952 double k_b = k2eff_dv[idx_dv];
953
954 std::complex<double> S_ij_k = coupling
956 dn_LM_for_sn, N_00, ylm_r_a, ylm_r_b, sj_a, sj_b,
957 Sbar_LM, k_a, k_b
958 ); // S|{i = j ≠ k}
959 update_progbar();
960
961 sn_dv[idx_dv] += factor_phase * (
962 S_ij_k + factor_mirror_w3j * std::conj(S_ij_k)
963 );
964 }
965
966 sph_orders_computed.push_back(sph_order_current);
967 count_terms++;
968 if (trvs::currTask == 0) {
969 trvs::logger.stat(
970 "Bispectrum term computed at orders (m₁, m₂, M) = ±(%d, %d, %d).",
971 m1_, m2_, M_
972 );
973 }
974 }
975 }
976 }
977
978 // ---------------------------------------------------------------------
979 // Results
980 // ---------------------------------------------------------------------
981
982 trv::BispecMeasurements bispec_out;
983 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
984 bispec_out.k1_bin.push_back(k1bin_dv[idx_dv]);
985 bispec_out.k1_eff.push_back(k1eff_dv[idx_dv]);
986 bispec_out.nmodes_1.push_back(nmodes1_dv[idx_dv]);
987 bispec_out.k2_bin.push_back(k2bin_dv[idx_dv]);
988 bispec_out.k2_eff.push_back(k2eff_dv[idx_dv]);
989 bispec_out.nmodes_2.push_back(nmodes2_dv[idx_dv]);
990 bispec_out.bk_raw.push_back(norm_factor * bk_dv[idx_dv]);
991 bispec_out.bk_shot.push_back(norm_factor * sn_dv[idx_dv]);
992 }
993 bispec_out.dim = dv_dim;
994
995 delete[] nmodes1_dv; delete[] nmodes2_dv;
996 delete[] k1bin_dv; delete[] k2bin_dv;
997 delete[] k1eff_dv; delete[] k2eff_dv;
998 delete[] bk_dv; delete[] sn_dv;
999
1000 trvs::count_cgrid -= 4;
1001 trvs::count_grid -= 4;
1004
1005 if (trvs::currTask == 0) {
1006 trvs::logger.stat(
1007 "... computed bispectrum from paired survey-type catalogues."
1008 );
1009 }
1010
1011 return bispec_out;
1012}
1013
1015 ParticleCatalogue& catalogue_data, ParticleCatalogue& catalogue_rand,
1016 LineOfSight* los_data, LineOfSight* los_rand,
1017 trv::ParameterSet& params, trv::Binning& rbinning,
1018 double norm_factor
1019) {
1020 trvs::logger.reset_level(params.verbose);
1021
1022 if (trvs::currTask == 0) {
1023 trvs::logger.stat(
1024 "Computing three-point correlation function "
1025 "from paired survey-type catalogues..."
1026 );
1027 }
1028
1029 // ---------------------------------------------------------------------
1030 // Set-up
1031 // ---------------------------------------------------------------------
1032
1033 // Set up/check input.
1035
1036 double alpha = catalogue_data.wstotal / catalogue_rand.wstotal;
1037
1038 std::complex<double> factor_phase =
1039 std::pow(trvm::M_I, params.ell1 + params.ell2);
1040 double factor_parity = std::pow(-1., params.ell1 + params.ell2);
1041
1042 // Set up output.
1043 int dv_dim = 0; // data vector dimension
1044 if (params.shape == "diag" || params.shape == "row" ) {
1045 dv_dim = rbinning.num_bins;
1046 } else
1047 if (params.shape == "off-diag") {
1048 dv_dim = rbinning.num_bins - std::abs(params.idx_bin);
1049 } else
1050 if (params.shape == "full") {
1051 dv_dim = rbinning.num_bins * rbinning.num_bins;
1052 } else
1053 if (params.shape == "triu") {
1054 dv_dim = rbinning.num_bins * (rbinning.num_bins + 1) / 2;
1055 } else {
1056 if (trvs::currTask == 0) {
1057 trvs::logger.error(
1058 "Three-point statistic form is not recognised: `form` = '%s'.",
1059 params.form.c_str()
1060 );
1061 }
1063 "Three-point statistic form is not recognised: `form` = '%s'.",
1064 params.form.c_str()
1065 );
1066 }
1067
1068 int* npairs1_dv = new int[dv_dim]();
1069 int* npairs2_dv = new int[dv_dim]();
1070 double* r1bin_dv = new double[dv_dim]();
1071 double* r2bin_dv = new double[dv_dim]();
1072 double* r1eff_dv = new double[dv_dim]();
1073 double* r2eff_dv = new double[dv_dim]();
1074 std::complex<double>* zeta_dv = new std::complex<double>[dv_dim];
1075 std::complex<double>* sn_dv = new std::complex<double>[dv_dim];
1076
1077 // ---------------------------------------------------------------------
1078 // Measurement
1079 // ---------------------------------------------------------------------
1080
1081#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
1082 if (!trvs::is_gpu_enabled()) {
1083 fftw_init_threads();
1084 }
1085#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
1086
1087 // Compute common field quantities.
1088 MeshField dn_00(params, true, "`dn_00`"); // δn_00(k)
1090 catalogue_data, catalogue_rand, los_data, los_rand, alpha, 0, 0
1091 );
1092 dn_00.fourier_transform();
1093
1094 double vol_cell = dn_00.vol_cell;
1095
1096 MeshField N_00(params, true, "`N_00`"); // N_00(k)
1098 catalogue_data, catalogue_rand, los_data, los_rand, alpha, 0, 0
1099 );
1100 N_00.fourier_transform();
1101
1102 trvm::SphericalBesselCalculator sj_a(params.ell1); // j_l_a
1103 trvm::SphericalBesselCalculator sj_b(params.ell2); // j_l_b
1104
1105 FieldStats stats_sn(params);
1106
1107 // Initialise reduced-spherical-harmonic weights on mesh grids.
1108 std::vector< std::complex<double> > ylm_r_a(params.nmesh);
1109 std::vector< std::complex<double> > ylm_r_b(params.nmesh);
1110 std::vector< std::complex<double> > ylm_k_a(params.nmesh);
1111 std::vector< std::complex<double> > ylm_k_b(params.nmesh);
1112 trvs::count_cgrid += 4;
1113 trvs::count_grid += 4;
1118
1119 // Compute 3PCF terms including shot noise.
1120 std::vector<trv::SphericalOrderTriplet> sph_orders_computed;
1121 int count_terms = 0;
1122 for (int m1_ = - params.ell1; m1_ <= params.ell1; m1_++) {
1123 for (int m2_ = - params.ell2; m2_ <= params.ell2; m2_++) {
1124 // Check for if all Wigner-3j symbols are zero.
1125 std::string flag_vanishing = "true";
1126 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
1127 double coupling = trv::calc_coupling_coeff_3pt(
1128 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
1129 );
1130 if (std::fabs(coupling) > trvm::eps_coupling) {
1131 flag_vanishing = "false";
1132 break;
1133 }
1134 }
1135 if (flag_vanishing == "true") {continue;}
1136
1139 params.ell1, m1_, params.boxsize, params.ngrid, ylm_r_a
1140 );
1143 params.ell2, m2_, params.boxsize, params.ngrid, ylm_r_b
1144 );
1147 params.ell1, m1_, params.boxsize, params.ngrid, ylm_k_a
1148 );
1151 params.ell2, m2_, params.boxsize, params.ngrid, ylm_k_b
1152 );
1153
1154 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
1155 // Check if the current spherical order triplet is redundant.
1156 SphericalOrderTriplet sph_order_current = {m1_, m2_, M_};
1157
1158 std::string flag_redundant = "false";
1159 for (SphericalOrderTriplet sph_order : sph_orders_computed) {
1160 if (sph_order_current.is_inverse(sph_order)) {
1161 flag_redundant = "true";
1162 }
1163 }
1164 if (flag_redundant == "true") {continue;}
1165
1166 // Calculate the coupling coefficient.
1167 double coupling = trv::calc_coupling_coeff_3pt(
1168 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
1169 ); // Wigner 3-j's
1170 if (std::fabs(coupling) < trvm::eps_coupling) {continue;}
1171
1172 // The factor should be
1173 // ```
1174 // std::pow(-1., params.ell1 + params.ell2 + params.ELL) *
1175 // std::pow(-1., m1_ + m2_ + M_) *
1176 // {std::pow(-1., params.ell1 + params.ell2), 1};
1177 // ```
1178 // which, by parity symmetry and Wigner 3-j properties,
1179 // simplifies to:
1180 double factor_mirror = sph_order_current.is_zeros() ?
1181 0. : std::pow(-1., params.ELL);
1182 double factor_mirror_w3j = sph_order_current.is_zeros() ?
1183 0. : 1.; // std::pow(-1., params.ell1 + params.ell2 + params.ELL);
1184
1185 // Set up FFT counter and progress bar.
1186 int progbar_task_count = 4 + 2 * dv_dim;
1187 std::stringstream progbar_name_ss;
1188 progbar_name_ss
1189 << "component orders (" << m1_ << ", " << m2_ << ", " << M_ << ")";
1190 trvs::ProgressBar progbar(progbar_task_count, progbar_name_ss.str());
1191
1192 std::vector<float> progbar_nodes;
1193 if (params.progbar != "false" && params.progbar != "true") {
1194 progbar_nodes = trvs::set_nodes_by_str(params.progbar);
1195 if (progbar_nodes.size() > 0) {
1196 progbar.set_nodes(progbar_nodes);
1197 }
1198 }
1199
1200 int count_fft_init = trvs::count_fft + trvs::count_ifft;
1201 auto update_progbar = [&]() {
1202 if (params.progbar != "false") {
1203 if (trvs::currTask == 0) {
1204 progbar.update(
1205 trvs::count_fft + trvs::count_ifft - count_fft_init
1206 );
1207 }
1208 }
1209 };
1210
1211 // ·······························································
1212 // Shot noise
1213 // ·······························································
1214
1215 // Compute shot noise components in eq. (51) in the Paper.
1216 MeshField dn_LM_for_sn(params, true, "`dn_LM_for_sn`"); // δn_LM(k)
1217 // (for
1218 // shot noise)
1219 dn_LM_for_sn.compute_ylm_wgtd_field(
1220 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
1221 params.ELL, M_
1222 );
1223 dn_LM_for_sn.fourier_transform();
1224 update_progbar();
1225
1226 std::complex<double> Sbar_LM = calc_ylm_wgtd_shotnoise_amp_for_bispec(
1227 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
1228 params.ELL, M_
1229 ); // \bar{S}_LM
1230
1232 dn_LM_for_sn, N_00, ylm_r_a, ylm_r_b, Sbar_LM, rbinning
1233 ); // S|{i = j ≠ k}
1234 update_progbar();
1235
1236 // Enforce the Kronecker delta in eq. (51) in the Paper.
1237 if (params.shape == "diag") {
1238 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1239 int ibin = idx_dv;
1240 sn_dv[idx_dv] += coupling * factor_parity * (
1241 stats_sn.xi[ibin]
1242 + factor_mirror_w3j * std::conj(stats_sn.xi[ibin])
1243 );
1244 }
1245 }
1246
1247 if (params.shape == "off-diag" && params.idx_bin == 0) {
1248 // Note that ``idx_col == idx_row``.
1249 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1250 int ibin = idx_dv;
1251 sn_dv[idx_dv] += coupling * factor_parity * (
1252 stats_sn.xi[ibin]
1253 + factor_mirror_w3j * std::conj(stats_sn.xi[ibin])
1254 );
1255 }
1256 }
1257
1258 if (params.shape == "row") {
1259 sn_dv[params.idx_bin] += coupling * factor_parity * (
1260 stats_sn.xi[params.idx_bin]
1261 + factor_mirror_w3j * std::conj(stats_sn.xi[params.idx_bin])
1262 );
1263 }
1264
1265 if (params.shape == "full") {
1266 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
1267 // Note that ``idx_col == idx_row``.
1268 int idx_dv = idx_row * params.num_bins + idx_row;
1269 sn_dv[idx_dv] += coupling * factor_parity * (
1270 stats_sn.xi[idx_row]
1271 + factor_mirror_w3j * std::conj(stats_sn.xi[idx_row])
1272 );
1273 }
1274 }
1275
1276 if (params.shape == "triu") {
1277 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
1278 // Note that ``idx_col == idx_row``.
1279 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2;
1280 sn_dv[idx_dv] += coupling * factor_parity * (
1281 stats_sn.xi[idx_row]
1282 + factor_mirror_w3j * std::conj(stats_sn.xi[idx_row])
1283 );
1284 }
1285 }
1286
1287 // Only record the binned coordinates and counts once.
1288 if (count_terms == 0) {
1289 if (params.shape == "diag") {
1290 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1291 int ibin = idx_dv;
1292
1293 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin];
1294 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin];
1295 r1eff_dv[idx_dv] = stats_sn.r[ibin];
1296 r2eff_dv[idx_dv] = stats_sn.r[ibin];
1297 npairs1_dv[idx_dv] = stats_sn.npairs[ibin];
1298 npairs2_dv[idx_dv] = stats_sn.npairs[ibin];
1299 }
1300 }
1301
1302 if (params.shape == "off-diag") {
1303 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1304 int ibin_row, ibin_col;
1305 if (params.idx_bin >= 0) {
1306 ibin_row = idx_dv;
1307 ibin_col = idx_dv + std::abs(params.idx_bin);
1308 } else {
1309 ibin_row = idx_dv + std::abs(params.idx_bin);
1310 ibin_col = idx_dv;
1311 }
1312
1313 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin_row];
1314 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin_col];
1315 r1eff_dv[idx_dv] = stats_sn.r[ibin_row];
1316 r2eff_dv[idx_dv] = stats_sn.r[ibin_col];
1317 npairs1_dv[idx_dv] = stats_sn.npairs[ibin_row];
1318 npairs2_dv[idx_dv] = stats_sn.npairs[ibin_col];
1319 }
1320 }
1321
1322 if (params.shape == "row") {
1323 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1324 int ibin_row = params.idx_bin;
1325 int ibin_col = idx_dv;
1326
1327 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin_row];
1328 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin_col];
1329 r1eff_dv[idx_dv] = stats_sn.r[ibin_row];
1330 r2eff_dv[idx_dv] = stats_sn.r[ibin_col];
1331 npairs1_dv[idx_dv] = stats_sn.npairs[ibin_row];
1332 npairs2_dv[idx_dv] = stats_sn.npairs[ibin_col];
1333 }
1334 }
1335
1336 if (params.shape == "full") {
1337 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
1338 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
1339 int idx_dv = idx_row * params.num_bins + idx_col;
1340
1341 r1bin_dv[idx_dv] = rbinning.bin_centres[idx_row];
1342 r2bin_dv[idx_dv] = rbinning.bin_centres[idx_col];
1343 r1eff_dv[idx_dv] = stats_sn.r[idx_row];
1344 r2eff_dv[idx_dv] = stats_sn.r[idx_col];
1345 npairs1_dv[idx_dv] = stats_sn.npairs[idx_row];
1346 npairs2_dv[idx_dv] = stats_sn.npairs[idx_col];
1347 }
1348 }
1349 }
1350
1351 if (params.shape == "triu") {
1352 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
1353 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++)
1354 {
1355 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
1356 + (idx_col - idx_row);
1357
1358 r1bin_dv[idx_dv] = rbinning.bin_centres[idx_row];
1359 r2bin_dv[idx_dv] = rbinning.bin_centres[idx_col];
1360 r1eff_dv[idx_dv] = stats_sn.r[idx_row];
1361 r2eff_dv[idx_dv] = stats_sn.r[idx_col];
1362 npairs1_dv[idx_dv] = stats_sn.npairs[idx_row];
1363 npairs2_dv[idx_dv] = stats_sn.npairs[idx_col];
1364 }
1365 }
1366 }
1367 }
1368
1369 // ·······························································
1370 // Raw 3PCF
1371 // ·······························································
1372
1373 // Compute 3PCF components in eqs. (42), (48) & (49) in the Paper.
1374 MeshField G_LM(params, true, "`G_LM`"); // G_LM
1376 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
1377 params.ELL, M_
1378 );
1379 G_LM.fourier_transform();
1381 G_LM.inv_fourier_transform();
1382 update_progbar();
1383
1384 MeshField F_lm_a(params, true, "`F_lm_a`"); // F_lm_a
1385 MeshField F_lm_b(params, true, "`F_lm_b`"); // F_lm_b
1386
1387 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1388 double r_a = r1eff_dv[idx_dv];
1390 dn_00, ylm_k_a, sj_a, r_a
1391 );
1392
1393 double r_b = r2eff_dv[idx_dv];
1395 dn_00, ylm_k_b, sj_b, r_b
1396 );
1397 update_progbar();
1398
1399 // ζ_{l₁ l₂ L}^{m₁ m₂ M}
1400 double zeta_comp_real = 0., zeta_comp_imag = 0.;
1401
1402#ifdef TRV_USE_OMP
1403#pragma omp parallel for reduction(+:zeta_comp_real, zeta_comp_imag)
1404#endif // TRV_USE_OMP
1405 for (long long gid = 0; gid < params.nmesh; gid++) {
1406 std::complex<double> F_lm_a_gridpt(F_lm_a[gid][0], F_lm_a[gid][1]);
1407 std::complex<double> F_lm_b_gridpt(F_lm_b[gid][0], F_lm_b[gid][1]);
1408 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
1409 std::complex<double> zeta_gridpt =
1410 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
1411
1412 zeta_comp_real += zeta_gridpt.real();
1413 zeta_comp_imag += zeta_gridpt.imag();
1414 }
1415
1416 std::complex<double> zeta_component(zeta_comp_real, zeta_comp_imag);
1417
1418 zeta_dv[idx_dv] += coupling * vol_cell * factor_phase * (
1419 zeta_component + factor_mirror * std::conj(zeta_component)
1420 );
1421 }
1422
1423 sph_orders_computed.push_back(sph_order_current);
1424 count_terms++;
1425 if (trvs::currTask == 0) {
1426 trvs::logger.stat(
1427 "Three-point correlation function term computed at orders "
1428 "(m₁, m₂, M) = ±(%d, %d, %d).",
1429 m1_, m2_, M_
1430 );
1431 }
1432 }
1433 }
1434 }
1435
1436 // ---------------------------------------------------------------------
1437 // Results
1438 // ---------------------------------------------------------------------
1439
1440 trv::ThreePCFMeasurements threepcf_out;
1441 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1442 threepcf_out.r1_bin.push_back(r1bin_dv[idx_dv]);
1443 threepcf_out.r1_eff.push_back(r1eff_dv[idx_dv]);
1444 threepcf_out.npairs_1.push_back(npairs1_dv[idx_dv]);
1445 threepcf_out.r2_bin.push_back(r2bin_dv[idx_dv]);
1446 threepcf_out.r2_eff.push_back(r2eff_dv[idx_dv]);
1447 threepcf_out.npairs_2.push_back(npairs2_dv[idx_dv]);
1448 threepcf_out.zeta_raw.push_back(norm_factor * zeta_dv[idx_dv]);
1449 threepcf_out.zeta_shot.push_back(norm_factor * sn_dv[idx_dv]);
1450 }
1451 threepcf_out.dim = dv_dim;
1452
1453 delete[] npairs1_dv; delete[] npairs2_dv;
1454 delete[] r1bin_dv; delete[] r2bin_dv;
1455 delete[] r1eff_dv; delete[] r2eff_dv;
1456 delete[] zeta_dv; delete[] sn_dv;
1457
1458 trvs::count_cgrid -= 4;
1459 trvs::count_grid -= 4;
1462
1463 if (trvs::currTask == 0) {
1464 trvs::logger.stat(
1465 "... computed three-point correlation function "
1466 "from paired survey-type catalogues."
1467 );
1468 }
1469
1470 return threepcf_out;
1471}
1472
1474 ParticleCatalogue& catalogue_data,
1475 trv::ParameterSet& params, trv::Binning kbinning,
1476 double norm_factor
1477) {
1478 trvs::logger.reset_level(params.verbose);
1479
1480 if (trvs::currTask == 0) {
1481 trvs::logger.stat(
1482 "Computing bispectrum from a periodic-box simulation-type catalogue "
1483 "in the global plane-parallel approximation..."
1484 );
1485 }
1486
1487 // ---------------------------------------------------------------------
1488 // Set-up
1489 // ---------------------------------------------------------------------
1490
1491 // Set up/check input.
1493
1494 std::complex<double> factor_phase =
1495 std::pow(trvm::M_I, params.ell1 + params.ell2);
1496
1497 // Set up output.
1498 int dv_dim = 0; // data vector dimension
1499 if (params.shape == "diag" || params.shape == "row" ) {
1500 dv_dim = kbinning.num_bins;
1501 } else
1502 if (params.shape == "off-diag") {
1503 dv_dim = kbinning.num_bins - std::abs(params.idx_bin);
1504 } else
1505 if (params.shape == "full") {
1506 dv_dim = kbinning.num_bins * kbinning.num_bins;
1507 } else
1508 if (params.shape == "triu") {
1509 dv_dim = kbinning.num_bins * (kbinning.num_bins + 1) / 2;
1510 } else {
1511 if (trvs::currTask == 0) {
1512 trvs::logger.error(
1513 "Three-point statistic form is not recognised: `form` = '%s'.",
1514 params.form.c_str()
1515 );
1516 }
1518 "Three-point statistic form is not recognised: `form` = '%s'.",
1519 params.form.c_str()
1520 );
1521 }
1522
1523 int* nmodes1_dv = new int[dv_dim]();
1524 int* nmodes2_dv = new int[dv_dim]();
1525 double* k1bin_dv = new double[dv_dim]();
1526 double* k2bin_dv = new double[dv_dim]();
1527 double* k1eff_dv = new double[dv_dim]();
1528 double* k2eff_dv = new double[dv_dim]();
1529 std::complex<double>* bk_dv = new std::complex<double>[dv_dim];
1530 std::complex<double>* sn_dv = new std::complex<double>[dv_dim];
1531
1532 // ---------------------------------------------------------------------
1533 // Measurement
1534 // ---------------------------------------------------------------------
1535
1536#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
1537 if (!trvs::is_gpu_enabled()) {
1538 fftw_init_threads();
1539 }
1540#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
1541
1542 // Compute common field quantities.
1543 MeshField dn_00(params, true, "`dn_00`"); // δn_00(k)
1545 dn_00.fourier_transform();
1546
1547 MeshField& dn_00_for_sn = dn_00; // δn_00(k) (for shot noise)
1548 MeshField& dn_L0_for_sn = dn_00; // δn_L0(k) (for shot noise)
1549
1550 double vol_cell = dn_00.vol_cell;
1551
1552 // Under the global plane-parallel approximation, y_{LM} = δᴰ_{M0}
1553 // (L-invariant) for the line-of-sight spherical harmonic.
1554 MeshField N_L0(params, true, "`N_L0`"); // N_L0(k)
1555 N_L0.compute_unweighted_field(catalogue_data);
1556 N_L0.fourier_transform();
1557
1558 MeshField& N_00 = N_L0; // N_00(k)
1559
1560 trvm::SphericalBesselCalculator sj_a(params.ell1); // j_l_a
1561 trvm::SphericalBesselCalculator sj_b(params.ell2); // j_l_b
1562
1563 FieldStats stats_sn(params);
1564
1565 // Initialise/reset spherical harmonic mesh grids.
1566 std::vector< std::complex<double> > ylm_k_a(params.nmesh);
1567 std::vector< std::complex<double> > ylm_k_b(params.nmesh);
1568 std::vector< std::complex<double> > ylm_r_a(params.nmesh);
1569 std::vector< std::complex<double> > ylm_r_b(params.nmesh);
1570 trvs::count_cgrid += 4;
1571 trvs::count_grid += 4;
1576
1577 // Compute bispectrum terms including shot noise.
1578 std::vector<trv::SphericalOrderTriplet> sph_orders_computed;
1579 int count_terms = 0;
1580 for (int m1_ = - params.ell1; m1_ <= params.ell1; m1_++) {
1581 for (int m2_ = - params.ell2; m2_ <= params.ell2; m2_++) {
1582 // Under the global plane-parallel approximation, δᴰ_{M0} enforces
1583 // M = 0 for any spherical-harmonic-weighted field fluctuations.
1584 int M_ = 0;
1585
1586 // Check if the current spherical order triplet is redundant.
1587 SphericalOrderTriplet sph_order_current = {m1_, m2_, M_};
1588
1589 std::string flag_redundant = "false";
1590 for (SphericalOrderTriplet sph_order : sph_orders_computed) {
1591 if (sph_order_current.is_inverse(sph_order)) {
1592 flag_redundant = "true";
1593 }
1594 }
1595 if (flag_redundant == "true") {continue;}
1596
1597 // Calculate the coupling coefficient.
1598 double coupling = trv::calc_coupling_coeff_3pt(
1599 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
1600 ); // Wigner 3-j's
1601 if (std::fabs(coupling) < trvm::eps_coupling) {continue;}
1602
1603 // The factor consist of products of
1604 // ```
1605 // {std::pow(-1., params.ell1 + params.ell2 + params.ELL),
1606 // 1},
1607 // {std::pow(-1., m1_ + m2_ + M_),
1608 // std::pow(-1., 2*M_)} ≡ 1,
1609 // {std::pow(-1., params.ell1 + params.ell2),
1610 // std::pow(-1., params.ELL)} ≡ std::pow(-1., params.ELL)}
1611 // ```
1612 // which, by parity symmetry & Wigner 3-j properties, simplify to:
1613 double factor_mirror = sph_order_current.is_zeros() ?
1614 0. : std::pow(-1., params.ELL);
1615 double factor_mirror_w3j = sph_order_current.is_zeros() ?
1616 0. : 1.; // std::pow(-1., params.ell1 + params.ell2 + params.ELL);
1617
1620 params.ell1, m1_, params.boxsize, params.ngrid, ylm_k_a
1621 );
1622 trvm::SphericalHarmonicCalculator
1623 ::store_reduced_spherical_harmonic_in_fourier_space(
1624 params.ell2, m2_, params.boxsize, params.ngrid, ylm_k_b
1625 );
1628 params.ell1, m1_, params.boxsize, params.ngrid, ylm_r_a
1629 );
1632 params.ell2, m2_, params.boxsize, params.ngrid, ylm_r_b
1633 );
1634
1635 // Set up FFT counter and progress bar.
1636 int progbar_task_count = 2 + 3 * dv_dim;
1637 std::stringstream progbar_name_ss;
1638 progbar_name_ss
1639 << "component orders (" << m1_ << ", " << m2_ << ", " << M_ << ")";
1640 trvs::ProgressBar progbar(progbar_task_count, progbar_name_ss.str());
1641
1642 std::vector<float> progbar_nodes;
1643 if (params.progbar != "false" && params.progbar != "true") {
1644 progbar_nodes = trvs::set_nodes_by_str(params.progbar);
1645 if (progbar_nodes.size() > 0) {
1646 progbar.set_nodes(progbar_nodes);
1647 }
1648 }
1649
1650 int count_fft_init = trvs::count_fft + trvs::count_ifft;
1651 auto update_progbar = [&]() {
1652 if (params.progbar != "false") {
1653 if (trvs::currTask == 0) {
1654 progbar.update(
1655 trvs::count_fft + trvs::count_ifft - count_fft_init
1656 );
1657 }
1658 }
1659 };
1660
1661 // ·································································
1662 // Raw bispectrum
1663 // ·································································
1664
1665 MeshField G_00(params, true, "`G_00`"); // G_00
1667 G_00.fourier_transform();
1669 G_00.inv_fourier_transform();
1670 update_progbar();
1671
1672 MeshField F_lm_a(params, true, "`F_lm_a`"); // F_lm_a
1673 MeshField F_lm_b(params, true, "`F_lm_b`"); // F_lm_b
1674
1675 if (params.shape == "diag") {
1676 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1677 int ibin = idx_dv;
1678
1679 double k_lower = kbinning.bin_edges[ibin];
1680 double k_upper = kbinning.bin_edges[ibin + 1];
1681
1682 double k_eff_a_, k_eff_b_;
1683 int nmodes_a_, nmodes_b_;
1684
1686 dn_00, ylm_k_a, k_lower, k_upper, k_eff_a_, nmodes_a_
1687 );
1689 dn_00, ylm_k_b, k_lower, k_upper, k_eff_b_, nmodes_b_
1690 );
1691 update_progbar();
1692
1693 if (count_terms == 0) {
1694 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin];
1695 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin];
1696 k1eff_dv[idx_dv] = k_eff_a_;
1697 k2eff_dv[idx_dv] = k_eff_b_;
1698 nmodes1_dv[idx_dv] = nmodes_a_;
1699 nmodes2_dv[idx_dv] = nmodes_b_;
1700 }
1701
1702 // B_{l₁ l₂ L}^{m₁ m₂ M}
1703 double bk_comp_real = 0., bk_comp_imag = 0.;
1704
1705#ifdef TRV_USE_OMP
1706#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
1707#endif // TRV_USE_OMP
1708 for (long long gid = 0; gid < params.nmesh; gid++) {
1709 std::complex<double> F_lm_a_gridpt(F_lm_a[gid][0], F_lm_a[gid][1]);
1710 std::complex<double> F_lm_b_gridpt(F_lm_b[gid][0], F_lm_b[gid][1]);
1711 std::complex<double> G_00_gridpt(G_00[gid][0], G_00[gid][1]);
1712 std::complex<double> bk_gridpt =
1713 F_lm_a_gridpt * F_lm_b_gridpt * G_00_gridpt;
1714
1715 bk_comp_real += bk_gridpt.real();
1716 bk_comp_imag += bk_gridpt.imag();
1717 }
1718
1719 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
1720
1721 bk_dv[idx_dv] += coupling * vol_cell * (
1722 bk_component + factor_mirror * std::conj(bk_component)
1723 );
1724 }
1725 }
1726
1727 if (params.shape == "off-diag") {
1728 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1729 int ibin_row, ibin_col;
1730 if (params.idx_bin >= 0) {
1731 ibin_row = idx_dv;
1732 ibin_col = idx_dv + std::abs(params.idx_bin);
1733 } else {
1734 ibin_row = idx_dv + std::abs(params.idx_bin);
1735 ibin_col = idx_dv;
1736 }
1737
1738 double k_lower_a = kbinning.bin_edges[ibin_row];
1739 double k_upper_a = kbinning.bin_edges[ibin_row + 1];
1740 double k_lower_b = kbinning.bin_edges[ibin_col];
1741 double k_upper_b = kbinning.bin_edges[ibin_col + 1];
1742
1743 double k_eff_a_, k_eff_b_;
1744 int nmodes_a_, nmodes_b_;
1745
1747 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
1748 );
1750 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
1751 );
1752 update_progbar();
1753
1754 if (count_terms == 0) {
1755 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin_row];
1756 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin_col];
1757 k1eff_dv[idx_dv] = k_eff_a_;
1758 k2eff_dv[idx_dv] = k_eff_b_;
1759 nmodes1_dv[idx_dv] = nmodes_a_;
1760 nmodes2_dv[idx_dv] = nmodes_b_;
1761 }
1762
1763 // B_{l₁ l₂ L}^{m₁ m₂ M}
1764 double bk_comp_real = 0., bk_comp_imag = 0.;
1765
1766#ifdef TRV_USE_OMP
1767#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
1768#endif // TRV_USE_OMP
1769 for (long long gid = 0; gid < params.nmesh; gid++) {
1770 std::complex<double> F_lm_a_gridpt(F_lm_a[gid][0], F_lm_a[gid][1]);
1771 std::complex<double> F_lm_b_gridpt(F_lm_b[gid][0], F_lm_b[gid][1]);
1772 std::complex<double> G_00_gridpt(G_00[gid][0], G_00[gid][1]);
1773 std::complex<double> bk_gridpt =
1774 F_lm_a_gridpt * F_lm_b_gridpt * G_00_gridpt;
1775
1776 bk_comp_real += bk_gridpt.real();
1777 bk_comp_imag += bk_gridpt.imag();
1778 }
1779
1780 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
1781
1782 bk_dv[idx_dv] += coupling * vol_cell * (
1783 bk_component + factor_mirror * std::conj(bk_component)
1784 );
1785 }
1786 }
1787
1788 if (params.shape == "row") {
1789 int ibin_row = params.idx_bin;
1790
1791 double k_lower_a = kbinning.bin_edges[ibin_row];
1792 double k_upper_a = kbinning.bin_edges[ibin_row + 1];
1793
1794 double k_eff_a_;
1795 int nmodes_a_;
1796
1798 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
1799 );
1800
1801 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1802 int ibin_col = idx_dv;
1803
1804 double k_lower_b = kbinning.bin_edges[ibin_col];
1805 double k_upper_b = kbinning.bin_edges[ibin_col + 1];
1806
1807 double k_eff_b_;
1808 int nmodes_b_;
1809
1811 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
1812 );
1813 update_progbar();
1814
1815 if (count_terms == 0) {
1816 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin_row];
1817 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin_col];
1818 k1eff_dv[idx_dv] = k_eff_a_;
1819 k2eff_dv[idx_dv] = k_eff_b_;
1820 nmodes1_dv[idx_dv] = nmodes_a_;
1821 nmodes2_dv[idx_dv] = nmodes_b_;
1822 }
1823
1824 // B_{l₁ l₂ L}^{m₁ m₂ M}
1825 double bk_comp_real = 0., bk_comp_imag = 0.;
1826
1827#ifdef TRV_USE_OMP
1828#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
1829#endif // TRV_USE_OMP
1830 for (long long gid = 0; gid < params.nmesh; gid++) {
1831 std::complex<double> F_lm_a_gridpt(F_lm_a[gid][0], F_lm_a[gid][1]);
1832 std::complex<double> F_lm_b_gridpt(F_lm_b[gid][0], F_lm_b[gid][1]);
1833 std::complex<double> G_00_gridpt(G_00[gid][0], G_00[gid][1]);
1834 std::complex<double> bk_gridpt =
1835 F_lm_a_gridpt * F_lm_b_gridpt * G_00_gridpt;
1836
1837 bk_comp_real += bk_gridpt.real();
1838 bk_comp_imag += bk_gridpt.imag();
1839 }
1840
1841 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
1842
1843 bk_dv[idx_dv] += coupling * vol_cell * (
1844 bk_component + factor_mirror * std::conj(bk_component)
1845 );
1846 }
1847 }
1848
1849 if (params.shape == "full") {
1850 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
1851 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
1852 int idx_dv = idx_row * params.num_bins + idx_col;
1853
1854 double k_lower_a = kbinning.bin_edges[idx_row];
1855 double k_upper_a = kbinning.bin_edges[idx_row + 1];
1856 double k_lower_b = kbinning.bin_edges[idx_col];
1857 double k_upper_b = kbinning.bin_edges[idx_col + 1];
1858
1859 double k_eff_a_, k_eff_b_;
1860 int nmodes_a_, nmodes_b_;
1861
1863 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
1864 );
1866 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
1867 );
1868 update_progbar();
1869
1870 if (count_terms == 0) {
1871 k1bin_dv[idx_dv] = kbinning.bin_centres[idx_row];
1872 k2bin_dv[idx_dv] = kbinning.bin_centres[idx_col];
1873 k1eff_dv[idx_dv] = k_eff_a_;
1874 k2eff_dv[idx_dv] = k_eff_b_;
1875 nmodes1_dv[idx_dv] = nmodes_a_;
1876 nmodes2_dv[idx_dv] = nmodes_b_;
1877 }
1878
1879 // B_{l₁ l₂ L}^{m₁ m₂ M}
1880 double bk_comp_real = 0., bk_comp_imag = 0.;
1881
1882#ifdef TRV_USE_OMP
1883#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
1884#endif // TRV_USE_OMP
1885 for (long long gid = 0; gid < params.nmesh; gid++) {
1886 std::complex<double> F_lm_a_gridpt(
1887 F_lm_a[gid][0], F_lm_a[gid][1]
1888 );
1889 std::complex<double> F_lm_b_gridpt(
1890 F_lm_b[gid][0], F_lm_b[gid][1]
1891 );
1892 std::complex<double> G_00_gridpt(G_00[gid][0], G_00[gid][1]);
1893 std::complex<double> bk_gridpt =
1894 F_lm_a_gridpt * F_lm_b_gridpt * G_00_gridpt;
1895
1896 bk_comp_real += bk_gridpt.real();
1897 bk_comp_imag += bk_gridpt.imag();
1898 }
1899
1900 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
1901
1902 bk_dv[idx_dv] += coupling * vol_cell * (
1903 bk_component + factor_mirror * std::conj(bk_component)
1904 );
1905 }
1906 }
1907 }
1908
1909 if (params.shape == "triu") {
1910 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
1911 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++) {
1912 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
1913 + (idx_col - idx_row);
1914
1915 double k_lower_a = kbinning.bin_edges[idx_row];
1916 double k_upper_a = kbinning.bin_edges[idx_row + 1];
1917 double k_lower_b = kbinning.bin_edges[idx_col];
1918 double k_upper_b = kbinning.bin_edges[idx_col + 1];
1919
1920 double k_eff_a_, k_eff_b_;
1921 int nmodes_a_, nmodes_b_;
1922
1924 dn_00, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
1925 );
1927 dn_00, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
1928 );
1929 update_progbar();
1930
1931 if (count_terms == 0) {
1932 k1bin_dv[idx_dv] = kbinning.bin_centres[idx_row];
1933 k2bin_dv[idx_dv] = kbinning.bin_centres[idx_col];
1934 k1eff_dv[idx_dv] = k_eff_a_;
1935 k2eff_dv[idx_dv] = k_eff_b_;
1936 nmodes1_dv[idx_dv] = nmodes_a_;
1937 nmodes2_dv[idx_dv] = nmodes_b_;
1938 }
1939
1940 // B_{l₁ l₂ L}^{m₁ m₂ M}
1941 double bk_comp_real = 0., bk_comp_imag = 0.;
1942
1943#ifdef TRV_USE_OMP
1944#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
1945#endif // TRV_USE_OMP
1946 for (long long gid = 0; gid < params.nmesh; gid++) {
1947 std::complex<double> F_lm_a_gridpt(
1948 F_lm_a[gid][0], F_lm_a[gid][1]
1949 );
1950 std::complex<double> F_lm_b_gridpt(
1951 F_lm_b[gid][0], F_lm_b[gid][1]
1952 );
1953 std::complex<double> G_00_gridpt(G_00[gid][0], G_00[gid][1]);
1954 std::complex<double> bk_gridpt =
1955 F_lm_a_gridpt * F_lm_b_gridpt * G_00_gridpt;
1956
1957 bk_comp_real += bk_gridpt.real();
1958 bk_comp_imag += bk_gridpt.imag();
1959 }
1960
1961 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
1962
1963 bk_dv[idx_dv] += coupling * vol_cell * (
1964 bk_component + factor_mirror * std::conj(bk_component)
1965 );
1966 }
1967 }
1968 }
1969
1970 // ·································································
1971 // Shot noise
1972 // ·································································
1973
1974 // Under the global plane-parallel approximation, y_{LM} = δᴰ_{M0}
1975 // (L-invariant) for the line-of-sight spherical harmonic.
1976 // Also note the field is unweighted from simulation sources.
1977 std::complex<double> Sbar_LM =
1978 double(catalogue_data.ntotal); // \bar{S}_LM
1979 std::complex<double> Sbar_L0 = Sbar_LM; // \bar{S}_L0
1980
1981 if (params.ell1 == 0 && params.ell2 == 0) {
1982 std::complex<double> S_ijk = coupling * Sbar_LM; // S|{i = j = k}
1983 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1984 sn_dv[idx_dv] += S_ijk;
1985 }
1986 }
1987
1988 if (params.ell2 == 0) { // S|{i ≠ j = k}
1989 // When l₂ = 0, the Wigner 3-j symbol enforces L = l₁.
1991 dn_00_for_sn, N_L0, Sbar_LM, params.ell1, m1_, kbinning
1992 );
1993
1994 if (params.shape == "diag") {
1995 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
1996 int ibin = idx_dv;
1997 std::complex<double> S_i_jk = coupling * (
1998 stats_sn.pk[ibin] - stats_sn.sn[ibin]
1999 );
2000 sn_dv[idx_dv] += S_i_jk + factor_mirror * std::conj(S_i_jk);
2001 }
2002 }
2003
2004 if (params.shape == "off-diag") {
2005 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2006 int ibin_row;
2007 if (params.idx_bin >= 0) {
2008 ibin_row = idx_dv;
2009 } else {
2010 ibin_row = idx_dv + std::abs(params.idx_bin);
2011 }
2012 std::complex<double> S_i_jk = coupling * (
2013 stats_sn.pk[ibin_row] - stats_sn.sn[ibin_row]
2014 );
2015 sn_dv[idx_dv] += S_i_jk + factor_mirror * std::conj(S_i_jk);
2016 }
2017 }
2018
2019 if (params.shape == "row") {
2020 std::complex<double> S_i_jk_row_ = coupling * (
2021 stats_sn.pk[params.idx_bin] - stats_sn.sn[params.idx_bin]
2022 );
2023 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2024 sn_dv[idx_dv] +=
2025 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
2026 }
2027 }
2028
2029 if (params.shape == "full") {
2030 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2031 std::complex<double> S_i_jk_row_ = coupling * (
2032 stats_sn.pk[idx_row] - stats_sn.sn[idx_row]
2033 );
2034 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
2035 int idx_dv = idx_row * params.num_bins + idx_col;
2036 sn_dv[idx_dv] +=
2037 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
2038 }
2039 }
2040 }
2041
2042 if (params.shape == "triu") {
2043 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2044 std::complex<double> S_i_jk_row_ = coupling * (
2045 stats_sn.pk[idx_row] - stats_sn.sn[idx_row]
2046 );
2047 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++) {
2048 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
2049 + (idx_col - idx_row);
2050 sn_dv[idx_dv] +=
2051 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
2052 }
2053 }
2054 }
2055 }
2056
2057 if (params.ell1 == 0) { // S|{j ≠ i = k}
2058 // When l₁ = 0, the Wigner 3-j symbol enforces L = l₂.
2060 dn_00_for_sn, N_L0, Sbar_LM, params.ell2, m2_, kbinning
2061 );
2062
2063 if (params.shape == "diag") {
2064 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2065 int ibin = idx_dv;
2066 std::complex<double> S_ik_j_ki = coupling * (
2067 stats_sn.pk[ibin] - stats_sn.sn[ibin]
2068 );
2069 sn_dv[idx_dv] += S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
2070 }
2071 }
2072
2073 if (params.shape == "off-diag") {
2074 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2075 int ibin_col;
2076 if (params.idx_bin >= 0) {
2077 ibin_col = idx_dv + params.idx_bin;
2078 } else {
2079 ibin_col = idx_dv;
2080 }
2081 std::complex<double> S_ik_j_ki = coupling * (
2082 stats_sn.pk[ibin_col] - stats_sn.sn[ibin_col]
2083 );
2084 sn_dv[idx_dv] += S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
2085 }
2086 }
2087
2088 if (params.shape == "row") {
2089 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2090 int ibin_col = idx_dv;
2091 std::complex<double> S_ik_j_ki = coupling * (
2092 stats_sn.pk[ibin_col] - stats_sn.sn[ibin_col]
2093 );
2094 sn_dv[idx_dv] += S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
2095 }
2096 }
2097
2098 if (params.shape == "full") {
2099 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
2100 std::complex<double> S_ik_j_ki_col_ = coupling * (
2101 stats_sn.pk[idx_col] - stats_sn.sn[idx_col]
2102 );
2103 for (int idx_row = 0; idx_row <= params.num_bins; idx_row++) {
2104 int idx_dv = idx_row * params.num_bins + idx_col;
2105 sn_dv[idx_dv] +=
2106 S_ik_j_ki_col_ + factor_mirror * std::conj(S_ik_j_ki_col_);
2107 }
2108 }
2109 }
2110
2111 if (params.shape == "triu") {
2112 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
2113 std::complex<double> S_ik_j_ki_col_ = coupling * (
2114 stats_sn.pk[idx_col] - stats_sn.sn[idx_col]
2115 );
2116 for (int idx_row = 0; idx_row <= idx_col; idx_row++) {
2117 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
2118 + (idx_col - idx_row);
2119 sn_dv[idx_dv] +=
2120 S_ik_j_ki_col_ + factor_mirror * std::conj(S_ik_j_ki_col_);
2121 }
2122 }
2123 }
2124 }
2125
2126 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2127 double k_a = k1eff_dv[idx_dv];
2128 double k_b = k2eff_dv[idx_dv];
2129
2130 std::complex<double> S_ij_k = coupling
2132 dn_L0_for_sn, N_00, ylm_r_a, ylm_r_b, sj_a, sj_b,
2133 Sbar_L0, k_a, k_b
2134 ); // S|{i = j ≠ k}
2135 update_progbar();
2136
2137 sn_dv[idx_dv] += factor_phase * (
2138 S_ij_k + factor_mirror_w3j * std::conj(S_ij_k)
2139 );
2140 }
2141
2142 sph_orders_computed.push_back(sph_order_current);
2143 count_terms++;
2144 if (trvs::currTask == 0) {
2145 trvs::logger.stat(
2146 "Bispectrum term computed at orders (m₁, m₂, M) = ±(%d, %d, 0).",
2147 m1_, m2_
2148 );
2149 }
2150 }
2151 }
2152
2153 // ---------------------------------------------------------------------
2154 // Results
2155 // ---------------------------------------------------------------------
2156
2157 trv::BispecMeasurements bispec_out;
2158 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2159 bispec_out.k1_bin.push_back(k1bin_dv[idx_dv]);
2160 bispec_out.k1_eff.push_back(k1eff_dv[idx_dv]);
2161 bispec_out.nmodes_1.push_back(nmodes1_dv[idx_dv]);
2162 bispec_out.k2_bin.push_back(k2bin_dv[idx_dv]);
2163 bispec_out.k2_eff.push_back(k2eff_dv[idx_dv]);
2164 bispec_out.nmodes_2.push_back(nmodes2_dv[idx_dv]);
2165 bispec_out.bk_raw.push_back(norm_factor * bk_dv[idx_dv]);
2166 bispec_out.bk_shot.push_back(norm_factor * sn_dv[idx_dv]);
2167 }
2168 bispec_out.dim = dv_dim;
2169
2170 delete[] nmodes1_dv; delete[] nmodes2_dv;
2171 delete[] k1bin_dv; delete[] k2bin_dv;
2172 delete[] k1eff_dv; delete[] k2eff_dv;
2173 delete[] bk_dv; delete[] sn_dv;
2174
2175 trvs::count_cgrid -= 4;
2176 trvs::count_grid -= 4;
2179
2180 if (trvs::currTask == 0) {
2181 trvs::logger.stat(
2182 "... computed bispectrum from a periodic-box simulation-type catalogue "
2183 "in the global plane-parallel approximation."
2184 );
2185 }
2186
2187 return bispec_out;
2188}
2189
2191 ParticleCatalogue& catalogue_data,
2192 trv::ParameterSet& params, trv::Binning& rbinning,
2193 double norm_factor
2194) {
2195 trvs::logger.reset_level(params.verbose);
2196
2197 if (trvs::currTask == 0) {
2198 trvs::logger.stat(
2199 "Computing three-point correlation function "
2200 "from a periodic-box simulation-type catalogue "
2201 "in the global plane-parallel approximation..."
2202 );
2203 }
2204
2205 // ---------------------------------------------------------------------
2206 // Set-up
2207 // ---------------------------------------------------------------------
2208
2209 // Set up/check input.
2211
2212 std::complex<double> factor_phase =
2213 std::pow(trvm::M_I, params.ell1 + params.ell2);
2214 double factor_parity = std::pow(-1., params.ell1 + params.ell2);
2215
2216 // Set up output.
2217 int dv_dim = 0; // data vector dimension
2218 if (params.shape == "diag" || params.shape == "row" ) {
2219 dv_dim = rbinning.num_bins;
2220 } else
2221 if (params.shape == "off-diag") {
2222 dv_dim = rbinning.num_bins - std::abs(params.idx_bin);
2223 } else
2224 if (params.shape == "full") {
2225 dv_dim = rbinning.num_bins * rbinning.num_bins;
2226 } else
2227 if (params.shape == "triu") {
2228 dv_dim = rbinning.num_bins * (rbinning.num_bins + 1) / 2;
2229 } else {
2230 if (trvs::currTask == 0) {
2231 trvs::logger.error(
2232 "Three-point statistic form is not recognised: `form` = '%s'.",
2233 params.form.c_str()
2234 );
2235 }
2237 "Three-point statistic form is not recognised: `form` = '%s'.",
2238 params.form.c_str()
2239 );
2240 }
2241
2242 int* npairs1_dv = new int[dv_dim]();
2243 int* npairs2_dv = new int[dv_dim]();
2244 double* r1bin_dv = new double[dv_dim]();
2245 double* r2bin_dv = new double[dv_dim]();
2246 double* r1eff_dv = new double[dv_dim]();
2247 double* r2eff_dv = new double[dv_dim]();
2248 std::complex<double>* zeta_dv = new std::complex<double>[dv_dim];
2249 std::complex<double>* sn_dv = new std::complex<double>[dv_dim];
2250
2251 // ---------------------------------------------------------------------
2252 // Measurement
2253 // ---------------------------------------------------------------------
2254
2255#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
2256 if (!trvs::is_gpu_enabled()) {
2257 fftw_init_threads();
2258 }
2259#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
2260
2261 // Compute common field quantities.
2262 MeshField dn_00(params, true, "`dn_00`"); // δn_00(k)
2264 dn_00.fourier_transform();
2265
2266 MeshField& dn_L0_for_sn = dn_00; // δn_L0(k)
2267
2268 double vol_cell = dn_00.vol_cell;
2269
2270 MeshField N_00(params, true, "`N_00`"); // N_00(k)
2271 N_00.compute_unweighted_field(catalogue_data);
2272 N_00.fourier_transform();
2273
2274 trvm::SphericalBesselCalculator sj_a(params.ell1); // j_l_a
2275 trvm::SphericalBesselCalculator sj_b(params.ell2); // j_l_b
2276
2277 FieldStats stats_sn(params);
2278
2279 // Initialise/reset spherical harmonic mesh grids.
2280 std::vector< std::complex<double> > ylm_r_a(params.nmesh);
2281 std::vector< std::complex<double> > ylm_r_b(params.nmesh);
2282 std::vector< std::complex<double> > ylm_k_a(params.nmesh);
2283 std::vector< std::complex<double> > ylm_k_b(params.nmesh);
2284 trvs::count_cgrid += 4;
2285 trvs::count_grid += 4;
2290
2291 // Compute 3PCF terms including shot noise.
2292 std::vector<trv::SphericalOrderTriplet> sph_orders_computed;
2293 int count_terms = 0;
2294 for (int m1_ = - params.ell1; m1_ <= params.ell1; m1_++) {
2295 for (int m2_ = - params.ell2; m2_ <= params.ell2; m2_++) {
2296 // Under the global plane-parallel approximation, δᴰ_{M0} enforces
2297 // M = 0 for any spherical-harmonic-weighted field fluctuations.
2298 int M_ = 0;
2299
2300 // Check if the current spherical order triplet is redundant.
2301 SphericalOrderTriplet sph_order_current = {m1_, m2_, M_};
2302
2303 std::string flag_redundant = "false";
2304 for (SphericalOrderTriplet sph_order : sph_orders_computed) {
2305 if (sph_order_current.is_inverse(sph_order)) {
2306 flag_redundant = "true";
2307 }
2308 }
2309 if (flag_redundant == "true") {continue;}
2310
2311 // Calculate the coupling coefficient.
2312 double coupling = trv::calc_coupling_coeff_3pt(
2313 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
2314 ); // Wigner 3-j's
2315 if (std::fabs(coupling) < trvm::eps_coupling) {continue;}
2316
2317 // The factor should be
2318 // ```
2319 // std::pow(-1., params.ell1 + params.ell2 + params.ELL) *
2320 // std::pow(-1., m1_ + m2_ + M_) *
2321 // {std::pow(-1., params.ell1 + params.ell2), 1};
2322 // ```
2323 // which, by parity symmetry and Wigner 3-j properties,
2324 // simplifies to:
2325 double factor_mirror = sph_order_current.is_zeros() ?
2326 0. : std::pow(-1., params.ELL);
2327 double factor_mirror_w3j = sph_order_current.is_zeros() ?
2328 0. : 1.; // std::pow(-1., params.ell1 + params.ell2 + params.ELL);
2329
2332 params.ell1, m1_, params.boxsize, params.ngrid, ylm_r_a
2333 );
2336 params.ell2, m2_, params.boxsize, params.ngrid, ylm_r_b
2337 );
2340 params.ell1, m1_, params.boxsize, params.ngrid, ylm_k_a
2341 );
2344 params.ell2, m2_, params.boxsize, params.ngrid, ylm_k_b
2345 );
2346
2347 // Set up FFT counter and progress bar.
2348 int progbar_task_count = 3 + 2 * dv_dim;
2349 std::stringstream progbar_name_ss;
2350 progbar_name_ss
2351 << "component orders (" << m1_ << ", " << m2_ << ", " << M_ << ")";
2352 trvs::ProgressBar progbar(progbar_task_count, progbar_name_ss.str());
2353
2354 std::vector<float> progbar_nodes;
2355 if (params.progbar != "false" && params.progbar != "true") {
2356 progbar_nodes = trvs::set_nodes_by_str(params.progbar);
2357 if (progbar_nodes.size() > 0) {
2358 progbar.set_nodes(progbar_nodes);
2359 }
2360 }
2361
2362 int count_fft_init = trvs::count_fft + trvs::count_ifft;
2363 auto update_progbar = [&]() {
2364 if (params.progbar != "false") {
2365 if (trvs::currTask == 0) {
2366 progbar.update(
2367 trvs::count_fft + trvs::count_ifft - count_fft_init
2368 );
2369 }
2370 }
2371 };
2372
2373 // ·································································
2374 // Shot noise
2375 // ·································································
2376
2377 // Under the global plane-parallel approximation, y_{LM} = δᴰ_{M0}
2378 // (L-invariant) for the line-of-sight spherical harmonic.
2379 // Also note the field is unweighted from simulation sources.
2380 std::complex<double> Sbar_L0 =
2381 double(catalogue_data.ntotal); // \bar{S}_L0
2382
2384 dn_L0_for_sn, N_00, ylm_r_a, ylm_r_b, Sbar_L0, rbinning
2385 ); // S|{i = j ≠ k}
2386 update_progbar();
2387
2388 // Enforce the Kronecker delta in eq. (51) in the Paper.
2389 if (params.shape == "diag") {
2390 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2391 int ibin = idx_dv;
2392 sn_dv[idx_dv] += coupling * factor_parity * (
2393 stats_sn.xi[ibin]
2394 + factor_mirror_w3j * std::conj(stats_sn.xi[ibin])
2395 );
2396 }
2397 }
2398
2399 if (params.shape == "off-diag" && params.idx_bin == 0) {
2400 // Note that ``idx_col == idx_row``.
2401 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2402 int ibin = idx_dv;
2403 sn_dv[idx_dv] += coupling * factor_parity * (
2404 stats_sn.xi[ibin]
2405 + factor_mirror_w3j * std::conj(stats_sn.xi[ibin])
2406 );
2407 }
2408 }
2409
2410 if (params.shape == "row") {
2411 sn_dv[params.idx_bin] += coupling * factor_parity * (
2412 stats_sn.xi[params.idx_bin]
2413 + factor_mirror_w3j * std::conj(stats_sn.xi[params.idx_bin])
2414 );
2415 }
2416
2417 if (params.shape == "full") {
2418 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2419 // Note that ``idx_col == idx_row``.
2420 int idx_dv = idx_row * params.num_bins + idx_row;
2421 sn_dv[idx_dv] += coupling * factor_parity * (
2422 stats_sn.xi[idx_row]
2423 + factor_mirror_w3j * std::conj(stats_sn.xi[idx_row])
2424 );
2425 }
2426 }
2427
2428 if (params.shape == "triu") {
2429 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2430 // Note that ``idx_col == idx_row``.
2431 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2;
2432 sn_dv[idx_dv] += coupling * factor_parity * (
2433 stats_sn.xi[idx_row]
2434 + factor_mirror_w3j * std::conj(stats_sn.xi[idx_row])
2435 );
2436 }
2437 }
2438
2439 // Only record the binned coordinates and counts once.
2440 if (count_terms == 0) {
2441 if (params.shape == "diag") {
2442 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2443 int ibin = idx_dv;
2444
2445 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin];
2446 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin];
2447 r1eff_dv[idx_dv] = stats_sn.r[ibin];
2448 r2eff_dv[idx_dv] = stats_sn.r[ibin];
2449 npairs1_dv[idx_dv] = stats_sn.npairs[ibin];
2450 npairs2_dv[idx_dv] = stats_sn.npairs[ibin];
2451 }
2452 }
2453
2454 if (params.shape == "off-diag") {
2455 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2456 int ibin_row, ibin_col;
2457 if (params.idx_bin >= 0) {
2458 ibin_row = idx_dv;
2459 ibin_col = idx_dv + std::abs(params.idx_bin);
2460 } else {
2461 ibin_row = idx_dv + std::abs(params.idx_bin);
2462 ibin_col = idx_dv;
2463 }
2464
2465 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin_row];
2466 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin_col];
2467 r1eff_dv[idx_dv] = stats_sn.r[ibin_row];
2468 r2eff_dv[idx_dv] = stats_sn.r[ibin_col];
2469 npairs1_dv[idx_dv] = stats_sn.npairs[ibin_row];
2470 npairs2_dv[idx_dv] = stats_sn.npairs[ibin_col];
2471 }
2472 }
2473
2474 if (params.shape == "row") {
2475 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2476 int ibin_row = params.idx_bin;
2477 int ibin_col = idx_dv;
2478
2479 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin_row];
2480 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin_col];
2481 r1eff_dv[idx_dv] = stats_sn.r[ibin_row];
2482 r2eff_dv[idx_dv] = stats_sn.r[ibin_col];
2483 npairs1_dv[idx_dv] = stats_sn.npairs[ibin_row];
2484 npairs2_dv[idx_dv] = stats_sn.npairs[ibin_col];
2485 }
2486 }
2487
2488 if (params.shape == "full") {
2489 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2490 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
2491 int idx_dv = idx_row * params.num_bins + idx_col;
2492
2493 r1bin_dv[idx_dv] = rbinning.bin_centres[idx_row];
2494 r2bin_dv[idx_dv] = rbinning.bin_centres[idx_col];
2495 r1eff_dv[idx_dv] = stats_sn.r[idx_row];
2496 r2eff_dv[idx_dv] = stats_sn.r[idx_col];
2497 npairs1_dv[idx_dv] = stats_sn.npairs[idx_row];
2498 npairs2_dv[idx_dv] = stats_sn.npairs[idx_col];
2499 }
2500 }
2501 }
2502
2503 if (params.shape == "triu") {
2504 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2505 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++) {
2506 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
2507 + (idx_col - idx_row);
2508
2509 r1bin_dv[idx_dv] = rbinning.bin_centres[idx_row];
2510 r2bin_dv[idx_dv] = rbinning.bin_centres[idx_col];
2511 r1eff_dv[idx_dv] = stats_sn.r[idx_row];
2512 r2eff_dv[idx_dv] = stats_sn.r[idx_col];
2513 npairs1_dv[idx_dv] = stats_sn.npairs[idx_row];
2514 npairs2_dv[idx_dv] = stats_sn.npairs[idx_col];
2515 }
2516 }
2517 }
2518 }
2519
2520 // ·································································
2521 // Raw 3PCF
2522 // ·································································
2523
2524 // Compute 3PCF components in eqs. (42), (48) & (49) in the Paper.
2525 MeshField G_00(params, true, "`G_00`"); // G_00
2527 G_00.fourier_transform();
2529 G_00.inv_fourier_transform();
2530 update_progbar();
2531
2532 MeshField F_lm_a(params, true, "`F_lm_a`"); // F_lm_a
2533 MeshField F_lm_b(params, true, "`F_lm_b`"); // F_lm_b
2534
2535 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2536 double r_a = r1eff_dv[idx_dv];
2538 dn_00, ylm_k_a, sj_a, r_a
2539 );
2540
2541 double r_b = r2eff_dv[idx_dv];
2543 dn_00, ylm_k_b, sj_b, r_b
2544 );
2545 update_progbar();
2546
2547 // ζ_{l₁ l₂ L}^{m₁ m₂ M}
2548 double zeta_comp_real = 0., zeta_comp_imag = 0.;
2549
2550#ifdef TRV_USE_OMP
2551#pragma omp parallel for reduction(+:zeta_comp_real, zeta_comp_imag)
2552#endif // TRV_USE_OMP
2553 for (long long gid = 0; gid < params.nmesh; gid++) {
2554 std::complex<double> F_lm_a_gridpt(F_lm_a[gid][0], F_lm_a[gid][1]);
2555 std::complex<double> F_lm_b_gridpt(F_lm_b[gid][0], F_lm_b[gid][1]);
2556 std::complex<double> G_00_gridpt(G_00[gid][0], G_00[gid][1]);
2557 std::complex<double> zeta_gridpt =
2558 F_lm_a_gridpt * F_lm_b_gridpt * G_00_gridpt;
2559
2560 zeta_comp_real += zeta_gridpt.real();
2561 zeta_comp_imag += zeta_gridpt.imag();
2562 }
2563
2564 std::complex<double> zeta_component(zeta_comp_real, zeta_comp_imag);
2565
2566 zeta_dv[idx_dv] += coupling * vol_cell * factor_phase * (
2567 zeta_component + factor_mirror * std::conj(zeta_component)
2568 );
2569 }
2570
2571 sph_orders_computed.push_back(sph_order_current);
2572 count_terms++;
2573 if (trvs::currTask == 0) {
2574 trvs::logger.stat(
2575 "Three-point correlation function term computed at orders "
2576 "(m₁, m₂, M) = ±(%d, %d, 0).",
2577 m1_, m2_
2578 );
2579 }
2580 }
2581 }
2582
2583 // ---------------------------------------------------------------------
2584 // Results
2585 // ---------------------------------------------------------------------
2586
2587 trv::ThreePCFMeasurements threepcf_out;
2588 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2589 threepcf_out.r1_bin.push_back(r1bin_dv[idx_dv]);
2590 threepcf_out.r1_eff.push_back(r1eff_dv[idx_dv]);
2591 threepcf_out.npairs_1.push_back(npairs1_dv[idx_dv]);
2592 threepcf_out.r2_bin.push_back(r2bin_dv[idx_dv]);
2593 threepcf_out.r2_eff.push_back(r2eff_dv[idx_dv]);
2594 threepcf_out.npairs_2.push_back(npairs2_dv[idx_dv]);
2595 threepcf_out.zeta_raw.push_back(norm_factor * zeta_dv[idx_dv]);
2596 threepcf_out.zeta_shot.push_back(norm_factor * sn_dv[idx_dv]);
2597 }
2598 threepcf_out.dim = dv_dim;
2599
2600 delete[] npairs1_dv; delete[] npairs2_dv;
2601 delete[] r1bin_dv; delete[] r2bin_dv;
2602 delete[] r1eff_dv; delete[] r2eff_dv;
2603 delete[] zeta_dv; delete[] sn_dv;
2604
2605 trvs::count_cgrid -= 4;
2606 trvs::count_grid -= 4;
2609
2610 if (trvs::currTask == 0) {
2611 trvs::logger.stat(
2612 "... computed three-point correlation function "
2613 "from a periodic-box simulation-type catalogue "
2614 "in the global plane-parallel approximation."
2615 );
2616 }
2617
2618 return threepcf_out;
2619}
2620
2622 ParticleCatalogue& catalogue_rand, LineOfSight* los_rand,
2623 trv::ParameterSet& params, trv::Binning& rbinning,
2624 double alpha, double norm_factor, bool wide_angle
2625) {
2626 std::string msg_tag = wide_angle ? "wide-angle corrections " : "";
2627
2628 trvs::logger.reset_level(params.verbose);
2629
2630 if (trvs::currTask == 0) {
2631 trvs::logger.stat(
2632 "Computing three-point correlation function window %s"
2633 "from random catalogue...",
2634 msg_tag.c_str()
2635 );
2636 }
2637
2638 // ---------------------------------------------------------------------
2639 // Set-up
2640 // ---------------------------------------------------------------------
2641
2642 // Set up/check input.
2644
2645 std::complex<double> factor_phase =
2646 std::pow(trvm::M_I, params.ell1 + params.ell2);
2647 double factor_parity = std::pow(-1., params.ell1 + params.ell2);
2648
2649 // Set up output.
2650 int dv_dim = 0; // data vector dimension
2651 if (params.shape == "diag" || params.shape == "row" ) {
2652 dv_dim = rbinning.num_bins;
2653 } else
2654 if (params.shape == "off-diag") {
2655 dv_dim = rbinning.num_bins - std::abs(params.idx_bin);
2656 } else
2657 if (params.shape == "full") {
2658 dv_dim = rbinning.num_bins * rbinning.num_bins;
2659 } else
2660 if (params.shape == "triu") {
2661 dv_dim = rbinning.num_bins * (rbinning.num_bins + 1) / 2;
2662 } else {
2663 if (trvs::currTask == 0) {
2664 trvs::logger.error(
2665 "Three-point statistic form is not recognised: `form` = '%s'.",
2666 params.form.c_str()
2667 );
2668 }
2670 "Three-point statistic form is not recognised: `form` = '%s'.",
2671 params.form.c_str()
2672 );
2673 }
2674
2675 int* npairs1_dv = new int[dv_dim]();
2676 int* npairs2_dv = new int[dv_dim]();
2677 double* r1bin_dv = new double[dv_dim]();
2678 double* r2bin_dv = new double[dv_dim]();
2679 double* r1eff_dv = new double[dv_dim]();
2680 double* r2eff_dv = new double[dv_dim]();
2681 std::complex<double>* zeta_dv = new std::complex<double>[dv_dim];
2682 std::complex<double>* sn_dv = new std::complex<double>[dv_dim];
2683
2684 // ---------------------------------------------------------------------
2685 // Measurement
2686 // ---------------------------------------------------------------------
2687
2688#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
2689 if (!trvs::is_gpu_enabled()) {
2690 fftw_init_threads();
2691 }
2692#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
2693
2694 // Compute common field quantities.
2695 MeshField n_00(params, true, "`n_00`"); // n_00(k)
2696 n_00.compute_ylm_wgtd_field(catalogue_rand, los_rand, alpha, 0, 0);
2697 n_00.fourier_transform();
2698
2699 double vol_cell = n_00.vol_cell;
2700
2701 MeshField N_00(params, true, "`N_00`"); // N_00(k)
2702 N_00.compute_ylm_wgtd_quad_field(catalogue_rand, los_rand, alpha, 0, 0);
2703 N_00.fourier_transform();
2704
2705 trvm::SphericalBesselCalculator sj_a(params.ell1); // j_l_a
2706 trvm::SphericalBesselCalculator sj_b(params.ell2); // j_l_b
2707
2708 FieldStats stats_sn(params);
2709
2710 // Initialise reduced-spherical-harmonic weights on mesh grids.
2711 std::vector< std::complex<double> > ylm_r_a(params.nmesh);
2712 std::vector< std::complex<double> > ylm_r_b(params.nmesh);
2713 std::vector< std::complex<double> > ylm_k_a(params.nmesh);
2714 std::vector< std::complex<double> > ylm_k_b(params.nmesh);
2715 trvs::count_cgrid += 4;
2716 trvs::count_grid += 4;
2721
2722 // Compute 3PCF window terms including shot noise.
2723 std::vector<trv::SphericalOrderTriplet> sph_orders_computed;
2724 int count_terms = 0;
2725 for (int m1_ = - params.ell1; m1_ <= params.ell1; m1_++) {
2726 for (int m2_ = - params.ell2; m2_ <= params.ell2; m2_++) {
2727 // Check for if all Wigner-3j symbols are zero.
2728 std::string flag_vanishing = "true";
2729 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
2730 double coupling = trv::calc_coupling_coeff_3pt(
2731 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
2732 );
2733 if (std::fabs(coupling) > trvm::eps_coupling) {
2734 flag_vanishing = "false";
2735 break;
2736 }
2737 }
2738 if (flag_vanishing == "true") {continue;}
2739
2742 params.ell1, m1_, params.boxsize, params.ngrid, ylm_r_a
2743 );
2746 params.ell2, m2_, params.boxsize, params.ngrid, ylm_r_b
2747 );
2750 params.ell1, m1_, params.boxsize, params.ngrid, ylm_k_a
2751 );
2754 params.ell2, m2_, params.boxsize, params.ngrid, ylm_k_b
2755 );
2756
2757 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
2758 // Check if the current spherical order triplet is redundant.
2759 SphericalOrderTriplet sph_order_current = {m1_, m2_, M_};
2760
2761 std::string flag_redundant = "false";
2762 for (SphericalOrderTriplet sph_order : sph_orders_computed) {
2763 if (sph_order_current.is_inverse(sph_order)) {
2764 flag_redundant = "true";
2765 }
2766 }
2767 if (flag_redundant == "true") {continue;}
2768
2769 // Calculate the coupling coefficient.
2770 double coupling = trv::calc_coupling_coeff_3pt(
2771 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
2772 ); // Wigner 3-j's
2773 if (std::fabs(coupling) < trvm::eps_coupling) {continue;}
2774
2775 // The factor should be
2776 // ```
2777 // std::pow(-1., params.ell1 + params.ell2 + params.ELL) *
2778 // std::pow(-1., m1_ + m2_ + M_) *
2779 // {std::pow(-1., params.ell1 + params.ell2), 1};
2780 // ```
2781 // which, by parity symmetry and Wigner 3-j properties,
2782 // simplifies to:
2783 double factor_mirror = sph_order_current.is_zeros() ?
2784 0. : std::pow(-1., params.ELL);
2785 double factor_mirror_w3j = sph_order_current.is_zeros() ?
2786 0. : 1.; // std::pow(-1., params.ell1 + params.ell2 + params.ELL);
2787
2788 // Set up FFT counter and progress bar.
2789 int progbar_task_count = 4 + 2 * dv_dim;
2790 std::stringstream progbar_name_ss;
2791 progbar_name_ss
2792 << "component orders (" << m1_ << ", " << m2_ << ", " << M_ << ")";
2793 trvs::ProgressBar progbar(progbar_task_count, progbar_name_ss.str());
2794
2795 std::vector<float> progbar_nodes;
2796 if (params.progbar != "false" && params.progbar != "true") {
2797 progbar_nodes = trvs::set_nodes_by_str(params.progbar);
2798 if (progbar_nodes.size() > 0) {
2799 progbar.set_nodes(progbar_nodes);
2800 }
2801 }
2802
2803 int count_fft_init = trvs::count_fft + trvs::count_ifft;
2804 auto update_progbar = [&]() {
2805 if (params.progbar != "false") {
2806 if (trvs::currTask == 0) {
2807 progbar.update(
2808 trvs::count_fft + trvs::count_ifft - count_fft_init
2809 );
2810 }
2811 }
2812 };
2813
2814 // ·······························································
2815 // Shot noise
2816 // ·······························································
2817
2818 // Compute shot noise components in eq. (51) in the Paper.
2819 MeshField n_LM_for_sn(params, true, "`n_LM_for_sn`"); // δn_LM(k)
2820 // (for
2821 // shot noise)
2822 n_LM_for_sn.compute_ylm_wgtd_field(
2823 catalogue_rand, los_rand, alpha, params.ELL, M_
2824 );
2825 n_LM_for_sn.fourier_transform();
2826 update_progbar();
2827
2828 std::complex<double> Sbar_LM = calc_ylm_wgtd_shotnoise_amp_for_bispec(
2829 catalogue_rand, los_rand, alpha, params.ELL, M_
2830 ); // \bar{S}_LM
2831
2833 n_LM_for_sn, N_00, ylm_r_a, ylm_r_b, Sbar_LM, rbinning
2834 ); // S|{i = j ≠ k}
2835 update_progbar();
2836
2837 // Enforce the Kronecker delta in eq. (51) in the Paper.
2838 if (params.shape == "diag") {
2839 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2840 int ibin = idx_dv;
2841 sn_dv[idx_dv] += coupling * factor_parity * (
2842 stats_sn.xi[ibin]
2843 + factor_mirror_w3j * std::conj(stats_sn.xi[ibin])
2844 );
2845 }
2846 }
2847
2848 if (params.shape == "off-diag" && params.idx_bin == 0) {
2849 // Note that ``idx_col == idx_row``.
2850 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2851 int ibin = idx_dv;
2852 sn_dv[idx_dv] += coupling * factor_parity * (
2853 stats_sn.xi[ibin]
2854 + factor_mirror_w3j * std::conj(stats_sn.xi[ibin])
2855 );
2856 }
2857 }
2858
2859 if (params.shape == "row") {
2860 sn_dv[params.idx_bin] += coupling * factor_parity * (
2861 stats_sn.xi[params.idx_bin]
2862 + factor_mirror_w3j * std::conj(stats_sn.xi[params.idx_bin])
2863 );
2864 }
2865
2866 if (params.shape == "full") {
2867 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2868 // Note that ``idx_col == idx_row``.
2869 int idx_dv = idx_row * params.num_bins + idx_row;
2870 sn_dv[idx_dv] += coupling * factor_parity * (
2871 stats_sn.xi[idx_row]
2872 + factor_mirror_w3j * std::conj(stats_sn.xi[idx_row])
2873 );
2874 }
2875 }
2876
2877 if (params.shape == "triu") {
2878 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2879 // Note that ``idx_col == idx_row``.
2880 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2;
2881 sn_dv[idx_dv] += coupling * factor_parity * (
2882 stats_sn.xi[idx_row]
2883 + factor_mirror_w3j * std::conj(stats_sn.xi[idx_row])
2884 );
2885 }
2886 }
2887
2888 if (count_terms == 0) {
2889 if (params.shape == "diag") {
2890 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2891 int ibin = idx_dv;
2892
2893 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin];
2894 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin];
2895 r1eff_dv[idx_dv] = stats_sn.r[ibin];
2896 r2eff_dv[idx_dv] = stats_sn.r[ibin];
2897 npairs1_dv[idx_dv] = stats_sn.npairs[ibin];
2898 npairs2_dv[idx_dv] = stats_sn.npairs[ibin];
2899 }
2900 }
2901
2902 if (params.shape == "off-diag") {
2903 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2904 int ibin_row, ibin_col;
2905 if (params.idx_bin >= 0) {
2906 ibin_row = idx_dv;
2907 ibin_col = idx_dv + std::abs(params.idx_bin);
2908 } else {
2909 ibin_row = idx_dv + std::abs(params.idx_bin);
2910 ibin_col = idx_dv;
2911 }
2912
2913 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin_row];
2914 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin_col];
2915 r1eff_dv[idx_dv] = stats_sn.r[ibin_row];
2916 r2eff_dv[idx_dv] = stats_sn.r[ibin_col];
2917 npairs1_dv[idx_dv] = stats_sn.npairs[ibin_row];
2918 npairs2_dv[idx_dv] = stats_sn.npairs[ibin_col];
2919 }
2920 }
2921
2922 if (params.shape == "row") {
2923 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2924 int ibin_row = params.idx_bin;
2925 int ibin_col = idx_dv;
2926
2927 r1bin_dv[idx_dv] = rbinning.bin_centres[ibin_row];
2928 r2bin_dv[idx_dv] = rbinning.bin_centres[ibin_col];
2929 r1eff_dv[idx_dv] = stats_sn.r[ibin_row];
2930 r2eff_dv[idx_dv] = stats_sn.r[ibin_col];
2931 npairs1_dv[idx_dv] = stats_sn.npairs[ibin_row];
2932 npairs2_dv[idx_dv] = stats_sn.npairs[ibin_col];
2933 }
2934 }
2935
2936 if (params.shape == "full") {
2937 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2938 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
2939 int idx_dv = idx_row * params.num_bins + idx_col;
2940
2941 r1bin_dv[idx_dv] = rbinning.bin_centres[idx_row];
2942 r2bin_dv[idx_dv] = rbinning.bin_centres[idx_col];
2943 r1eff_dv[idx_dv] = stats_sn.r[idx_row];
2944 r2eff_dv[idx_dv] = stats_sn.r[idx_col];
2945 npairs1_dv[idx_dv] = stats_sn.npairs[idx_row];
2946 npairs2_dv[idx_dv] = stats_sn.npairs[idx_col];
2947 }
2948 }
2949 }
2950
2951 if (params.shape == "triu") {
2952 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
2953 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++)
2954 {
2955 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
2956 + (idx_col - idx_row);
2957
2958 r1bin_dv[idx_dv] = rbinning.bin_centres[idx_row];
2959 r2bin_dv[idx_dv] = rbinning.bin_centres[idx_col];
2960 r1eff_dv[idx_dv] = stats_sn.r[idx_row];
2961 r2eff_dv[idx_dv] = stats_sn.r[idx_col];
2962 npairs1_dv[idx_dv] = stats_sn.npairs[idx_row];
2963 npairs2_dv[idx_dv] = stats_sn.npairs[idx_col];
2964 }
2965 }
2966 }
2967 }
2968
2969 // ·······························································
2970 // Raw 3PCF
2971 // ·······························································
2972
2973 // Compute 3PCF components in eqs. (42), (48) & (49) in the Paper.
2974 MeshField G_LM(params, true, "`G_LM`"); // G_LM
2976 catalogue_rand, los_rand, alpha, params.ELL, M_
2977 );
2978 G_LM.fourier_transform();
2980 G_LM.inv_fourier_transform();
2981 update_progbar();
2982
2983 // Perform wide-angle corrections if required.
2984 if (wide_angle) {
2986 }
2987
2988 MeshField F_lm_a(params, true, "`F_lm_a`"); // F_lm_a
2989 MeshField F_lm_b(params, true, "`F_lm_b`"); // F_lm_b
2990
2991 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
2992 double r_a = r1eff_dv[idx_dv];
2994 n_00, ylm_k_a, sj_a, r_a
2995 );
2996
2997 double r_b = r2eff_dv[idx_dv];
2999 n_00, ylm_k_b, sj_b, r_b
3000 );
3001
3002 update_progbar();
3003
3004 // ζ_{l₁ l₂ L}^{m₁ m₂ M}
3005 double zeta_comp_real = 0., zeta_comp_imag = 0.;
3006
3007#ifdef TRV_USE_OMP
3008#pragma omp parallel for reduction(+:zeta_comp_real, zeta_comp_imag)
3009#endif // TRV_USE_OMP
3010 for (long long gid = 0; gid < params.nmesh; gid++) {
3011 std::complex<double> F_lm_a_gridpt(F_lm_a[gid][0], F_lm_a[gid][1]);
3012 std::complex<double> F_lm_b_gridpt(F_lm_b[gid][0], F_lm_b[gid][1]);
3013 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
3014 std::complex<double> zeta_gridpt =
3015 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
3016
3017 zeta_comp_real += zeta_gridpt.real();
3018 zeta_comp_imag += zeta_gridpt.imag();
3019 }
3020
3021 std::complex<double> zeta_component(zeta_comp_real, zeta_comp_imag);
3022
3023 zeta_dv[idx_dv] += coupling * vol_cell * factor_phase * (
3024 zeta_component + factor_mirror * std::conj(zeta_component)
3025 );
3026 }
3027
3028 sph_orders_computed.push_back(sph_order_current);
3029 count_terms++;
3030 if (trvs::currTask == 0) {
3031 trvs::logger.stat(
3032 "Three-point correlation function window term computed at orders "
3033 "(m₁, m₂, M) = ±(%d, %d, %d).",
3034 m1_, m2_, M_
3035 );
3036 }
3037 }
3038 }
3039 }
3040
3041 // ---------------------------------------------------------------------
3042 // Results
3043 // ---------------------------------------------------------------------
3044
3045 trv::ThreePCFWindowMeasurements threepcfwin_out;
3046 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3047 threepcfwin_out.r1_bin.push_back(r1bin_dv[idx_dv]);
3048 threepcfwin_out.r1_eff.push_back(r1eff_dv[idx_dv]);
3049 threepcfwin_out.npairs_1.push_back(npairs1_dv[idx_dv]);
3050 threepcfwin_out.r2_bin.push_back(r2bin_dv[idx_dv]);
3051 threepcfwin_out.r2_eff.push_back(r2eff_dv[idx_dv]);
3052 threepcfwin_out.npairs_2.push_back(npairs2_dv[idx_dv]);
3053 threepcfwin_out.zeta_raw.push_back(norm_factor * zeta_dv[idx_dv]);
3054 threepcfwin_out.zeta_shot.push_back(norm_factor * sn_dv[idx_dv]);
3055 }
3056 threepcfwin_out.dim = dv_dim;
3057
3058 delete[] npairs1_dv; delete[] npairs2_dv;
3059 delete[] r1bin_dv; delete[] r2bin_dv;
3060 delete[] r1eff_dv; delete[] r2eff_dv;
3061 delete[] zeta_dv; delete[] sn_dv;
3062
3063 trvs::count_cgrid -= 4;
3064 trvs::count_grid -= 4;
3067
3068 if (trvs::currTask == 0) {
3069 trvs::logger.stat(
3070 "... computed three-point correlation function window %s"
3071 "from random catalogue.",
3072 msg_tag.c_str()
3073 );
3074 }
3075
3076 return threepcfwin_out;
3077}
3078
3079#ifdef TRV_USE_LEGACY_CODE
3080trv::BispecMeasurements compute_bispec_for_los_choice(
3081 ParticleCatalogue& catalogue_data, ParticleCatalogue& catalogue_rand,
3082 LineOfSight* los_data, LineOfSight* los_rand,
3083 int los_choice,
3084 trv::ParameterSet& params, trv::Binning& kbinning,
3085 double norm_factor
3086) {
3087 trvs::logger.reset_level(params.verbose);
3088
3089 if (trvs::currTask == 0) {
3090 trvs::logger.stat(
3091 "Computing bispectrum from paired survey-type catalogues "
3092 "for line-of-sight choice %d...",
3093 los_choice
3094 );
3095 }
3096
3097 // ---------------------------------------------------------------------
3098 // Set-up
3099 // ---------------------------------------------------------------------
3100
3101 // Set up/check input.
3103
3104 double alpha = catalogue_data.wstotal / catalogue_rand.wstotal;
3105
3106 std::complex<double> factor_phase =
3107 std::pow(trvm::M_I, params.ell1 + params.ell2);
3108
3109 // Set up output.
3110 int dv_dim = 0; // data vector dimension
3111 if (params.shape == "diag" || params.shape == "row" ) {
3112 dv_dim = kbinning.num_bins;
3113 } else
3114 if (params.shape == "off-diag") {
3115 dv_dim = kbinning.num_bins - std::abs(params.idx_bin);
3116 } else
3117 if (params.shape == "full") {
3118 dv_dim = kbinning.num_bins * kbinning.num_bins;
3119 } else
3120 if (params.shape == "triu") {
3121 dv_dim = kbinning.num_bins * (kbinning.num_bins + 1) / 2;
3122 } else {
3123 if (trvs::currTask == 0) {
3125 "Three-point statistic form is not recognised: `form` = '%s'.",
3126 params.form.c_str()
3127 );
3128 }
3129 throw trvs::InvalidParameterError(
3130 "Three-point statistic form is not recognised: `form` = '%s'.",
3131 params.form.c_str()
3132 );
3133 }
3134
3135 int* nmodes1_dv = new int[dv_dim]();
3136 int* nmodes2_dv = new int[dv_dim]();
3137 double* k1bin_dv = new double[dv_dim]();
3138 double* k2bin_dv = new double[dv_dim]();
3139 double* k1eff_dv = new double[dv_dim]();
3140 double* k2eff_dv = new double[dv_dim]();
3141 std::complex<double>* bk_dv = new std::complex<double>[dv_dim];
3142 std::complex<double>* sn_dv = new std::complex<double>[dv_dim];
3143
3144 // ---------------------------------------------------------------------
3145 // Measurement
3146 // ---------------------------------------------------------------------
3147
3148#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
3149 if (!trvs::is_gpu_enabled()) {
3150 fftw_init_threads();
3151 }
3152#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
3153
3154 // RFE: Not adopted until copy-assignment constructor is checked.
3155 // ---->
3156 // // Compute common field quantities.
3157 // MeshField dn_00(params, true, "`dn_00`"); // δn_00(r)
3158 // dn_00.compute_ylm_wgtd_field(
3159 // catalogue_data, catalogue_rand, los_data, los_rand, alpha, 0, 0
3160 // );
3161
3162 // double vol_cell = dn_00.vol_cell;
3163
3164 // MeshField N_00(params, true, "`N_00`"); // N_00(r)
3165 // N_00.compute_ylm_wgtd_quad_field(
3166 // catalogue_data, catalogue_rand, los_data, los_rand, alpha, 0, 0
3167 // );
3168 // ----<
3169
3170 trvm::SphericalBesselCalculator sj_a(params.ell1); // j_l_a
3171 trvm::SphericalBesselCalculator sj_b(params.ell2); // j_l_b
3172
3173 FieldStats stats_sn(params);
3174
3175 // Initialise reduced-spherical-harmonic weights on mesh grids.
3176 std::vector< std::complex<double> > ylm_k_a(params.nmesh);
3177 std::vector< std::complex<double> > ylm_k_b(params.nmesh);
3178 std::vector< std::complex<double> > ylm_r_a(params.nmesh);
3179 std::vector< std::complex<double> > ylm_r_b(params.nmesh);
3180 trvs::count_cgrid += 4;
3181 trvs::count_grid += 4;
3186
3187 // Compute bispectrum terms.
3188 std::vector<trv::SphericalOrderTriplet> sph_orders_computed;
3189 int count_terms = 0;
3190 for (int m1_ = - params.ell1; m1_ <= params.ell1; m1_++) {
3191 for (int m2_ = - params.ell2; m2_ <= params.ell2; m2_++) {
3192 // Check for if all Wigner-3j symbols are zero.
3193 std::string flag_vanishing = "true";
3194 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
3195 double coupling = trv::calc_coupling_coeff_3pt(
3196 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
3197 );
3198 if (std::fabs(coupling) > trvm::eps_coupling) {
3199 flag_vanishing = "false";
3200 break;
3201 }
3202 }
3203 if (flag_vanishing == "true") {continue;}
3204
3207 params.ell1, m1_, params.boxsize, params.ngrid, ylm_k_a
3208 );
3211 params.ell2, m2_, params.boxsize, params.ngrid, ylm_k_b
3212 );
3215 params.ell1, m1_, params.boxsize, params.ngrid, ylm_r_a
3216 );
3219 params.ell2, m2_, params.boxsize, params.ngrid, ylm_r_b
3220 );
3221
3222 for (int M_ = - params.ELL; M_ <= params.ELL; M_++) {
3223 // Check if the current spherical order triplet is redundant.
3224 SphericalOrderTriplet sph_order_current = {m1_, m2_, M_};
3225
3226 std::string flag_redundant = "false";
3227 for (SphericalOrderTriplet sph_order : sph_orders_computed) {
3228 if (sph_order_current.is_inverse(sph_order)) {
3229 flag_redundant = "true";
3230 }
3231 }
3232 if (flag_redundant == "true") {continue;}
3233
3234 // Calculate the coupling coefficient.
3235 double coupling = trv::calc_coupling_coeff_3pt(
3236 params.ell1, params.ell2, params.ELL, m1_, m2_, M_
3237 ); // Wigner 3-j's
3238 if (std::fabs(coupling) < trvm::eps_coupling) {continue;}
3239
3240 // The factors consist of products of
3241 // ```
3242 // {std::pow(-1., params.ell1 + params.ell2 + params.ELL),
3243 // 1},
3244 // {std::pow(-1., m1_ + m2_ + M_),
3245 // std::pow(-1., 2*M_)} ≡ 1,
3246 // {std::pow(-1., params.ell1 + params.ell2),
3247 // std::pow(-1., params.ELL)} ≡ std::pow(-1., params.ELL)}
3248 // ```
3249 // which, by parity symmetry & Wigner 3-j properties, simplify to:
3250 double factor_mirror = sph_order_current.is_zeros() ?
3251 0. : std::pow(-1., params.ELL);
3252 double factor_mirror_w3j = sph_order_current.is_zeros() ?
3253 0. : 1.; // std::pow(-1., params.ell1 + params.ell2 + params.ELL);
3254
3255 // Set up FFT counter and progress bar.
3256 int progbar_task_count = 10 + 3 * dv_dim;
3257 std::stringstream progbar_name_ss;
3258 progbar_name_ss
3259 << "component orders (" << m1_ << ", " << m2_ << ", " << M_ << ")";
3260 trvs::ProgressBar progbar(progbar_task_count, progbar_name_ss.str());
3261
3262 std::vector<float> progbar_nodes;
3263 if (params.progbar != "false" && params.progbar != "true") {
3264 progbar_nodes = trvs::set_nodes_by_str(params.progbar);
3265 if (progbar_nodes.size() > 0) {
3266 progbar.set_nodes(progbar_nodes);
3267 }
3268 }
3269
3270 int count_fft_init = trvs::count_fft + trvs::count_ifft;
3271 auto update_progbar = [&]() {
3272 if (params.progbar != "false") {
3273 if (trvs::currTask == 0) {
3274 progbar.update(
3275 trvs::count_fft + trvs::count_ifft - count_fft_init
3276 );
3277 }
3278 }
3279 };
3280
3281 // ·······························································
3282 // Raw bispectrum
3283 // ·······························································
3284
3285 // Compute bispectrum components in eqs. (41) & (42) in the Paper.
3286 MeshField dn_LM_a(params, true, "`dn_LM_a`"); // δn_LM_a
3287 if (los_choice == 0) {
3288 dn_LM_a.compute_ylm_wgtd_field(
3289 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3290 params.ELL, M_
3291 );
3292 } else {
3293 dn_LM_a.compute_ylm_wgtd_field(
3294 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3295 0, 0
3296 );
3297 }
3298 dn_LM_a.fourier_transform();
3299 update_progbar();
3300
3301 MeshField dn_LM_b(params, true, "`dn_LM_b`"); // δn_LM_b
3302 if (los_choice == 1) {
3303 dn_LM_b.compute_ylm_wgtd_field(
3304 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3305 params.ELL, M_
3306 );
3307 } else {
3308 dn_LM_b.compute_ylm_wgtd_field(
3309 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3310 0, 0
3311 );
3312 }
3313 dn_LM_b.fourier_transform();
3314 update_progbar();
3315
3316 MeshField G_LM(params, true, "`G_LM`"); // G_LM
3317 if (los_choice == 2) {
3318 G_LM.compute_ylm_wgtd_field(
3319 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3320 params.ELL, M_
3321 );
3322 } else {
3323 G_LM.compute_ylm_wgtd_field(
3324 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3325 0, 0
3326 );
3327 }
3328 G_LM.fourier_transform();
3329 G_LM.apply_assignment_compensation();
3330 G_LM.inv_fourier_transform();
3331 update_progbar();
3332
3333 double vol_cell = G_LM.vol_cell;
3334
3335 MeshField F_lm_a(params, true, "`F_lm_a`"); // F_lm_a
3336 MeshField F_lm_b(params, true, "`F_lm_b`"); // F_lm_b
3337
3338 if (params.shape == "diag") {
3339 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3340 int ibin = idx_dv;
3341
3342 double k_lower = kbinning.bin_edges[ibin];
3343 double k_upper = kbinning.bin_edges[ibin + 1];
3344
3345 double k_eff_a_, k_eff_b_;
3346 int nmodes_a_, nmodes_b_;
3347
3348 F_lm_a.inv_fourier_transform_ylm_wgtd_field_band_limited(
3349 dn_LM_a, ylm_k_a, k_lower, k_upper, k_eff_a_, nmodes_a_
3350 );
3351 F_lm_b.inv_fourier_transform_ylm_wgtd_field_band_limited(
3352 dn_LM_b, ylm_k_b, k_lower, k_upper, k_eff_b_, nmodes_b_
3353 );
3354 update_progbar();
3355
3356 if (count_terms == 0) {
3357 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin];
3358 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin];
3359 k1eff_dv[idx_dv] = k_eff_a_;
3360 k2eff_dv[idx_dv] = k_eff_b_;
3361 nmodes1_dv[idx_dv] = nmodes_a_;
3362 nmodes2_dv[idx_dv] = nmodes_b_;
3363 }
3364
3365 // B_{l₁ l₂ L}^{m₁ m₂ M}
3366 double bk_comp_real = 0., bk_comp_imag = 0.;
3367
3368#ifdef TRV_USE_OMP
3369#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
3370#endif // TRV_USE_OMP
3371 for (long long gid = 0; gid < params.nmesh; gid++) {
3372 std::complex<double> F_lm_a_gridpt(
3373 F_lm_a[gid][0], F_lm_a[gid][1]
3374 );
3375 std::complex<double> F_lm_b_gridpt(
3376 F_lm_b[gid][0], F_lm_b[gid][1]
3377 );
3378 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
3379 std::complex<double> bk_gridpt =
3380 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
3381
3382 bk_comp_real += bk_gridpt.real();
3383 bk_comp_imag += bk_gridpt.imag();
3384 }
3385
3386 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
3387
3388 bk_dv[idx_dv] += coupling * vol_cell * (
3389 bk_component + factor_mirror * std::conj(bk_component)
3390 );
3391 }
3392 }
3393
3394 if (params.shape == "off-diag") {
3395 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3396 int ibin_row, ibin_col;
3397 if (params.idx_bin >= 0) {
3398 ibin_row = idx_dv;
3399 ibin_col = idx_dv + std::abs(params.idx_bin);
3400 } else {
3401 ibin_row = idx_dv + std::abs(params.idx_bin);
3402 ibin_col = idx_dv;
3403 }
3404
3405 double k_lower_a = kbinning.bin_edges[ibin_row];
3406 double k_upper_a = kbinning.bin_edges[ibin_row + 1];
3407 double k_lower_b = kbinning.bin_edges[ibin_col];
3408 double k_upper_b = kbinning.bin_edges[ibin_col + 1];
3409
3410 double k_eff_a_, k_eff_b_;
3411 int nmodes_a_, nmodes_b_;
3412
3413 F_lm_a.inv_fourier_transform_ylm_wgtd_field_band_limited(
3414 dn_LM_a, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
3415 );
3416 F_lm_b.inv_fourier_transform_ylm_wgtd_field_band_limited(
3417 dn_LM_b, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
3418 );
3419 update_progbar();
3420
3421 if (count_terms == 0) {
3422 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin_row];
3423 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin_col];
3424 k1eff_dv[idx_dv] = k_eff_a_;
3425 k2eff_dv[idx_dv] = k_eff_b_;
3426 nmodes1_dv[idx_dv] = nmodes_a_;
3427 nmodes2_dv[idx_dv] = nmodes_b_;
3428 }
3429
3430 // B_{l₁ l₂ L}^{m₁ m₂ M}
3431 double bk_comp_real = 0., bk_comp_imag = 0.;
3432
3433#ifdef TRV_USE_OMP
3434#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
3435#endif // TRV_USE_OMP
3436 for (long long gid = 0; gid < params.nmesh; gid++) {
3437 std::complex<double> F_lm_a_gridpt(
3438 F_lm_a[gid][0], F_lm_a[gid][1]
3439 );
3440 std::complex<double> F_lm_b_gridpt(
3441 F_lm_b[gid][0], F_lm_b[gid][1]
3442 );
3443 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
3444 std::complex<double> bk_gridpt =
3445 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
3446
3447 bk_comp_real += bk_gridpt.real();
3448 bk_comp_imag += bk_gridpt.imag();
3449 }
3450
3451 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
3452
3453 bk_dv[idx_dv] += coupling * vol_cell * (
3454 bk_component + factor_mirror * std::conj(bk_component)
3455 );
3456 }
3457 }
3458
3459 if (params.shape == "row") {
3460 int ibin_row = params.idx_bin;
3461
3462 double k_lower_a = kbinning.bin_edges[params.idx_bin];
3463 double k_upper_a = kbinning.bin_edges[params.idx_bin + 1];
3464
3465 double k_eff_a_;
3466 int nmodes_a_;
3467
3468 F_lm_a.inv_fourier_transform_ylm_wgtd_field_band_limited(
3469 dn_LM_a, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
3470 );
3471
3472 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3473 int ibin_col = idx_dv;
3474
3475 double k_lower_b = kbinning.bin_edges[ibin_col];
3476 double k_upper_b = kbinning.bin_edges[ibin_col + 1];
3477
3478 double k_eff_b_;
3479 int nmodes_b_;
3480
3481 F_lm_b.inv_fourier_transform_ylm_wgtd_field_band_limited(
3482 dn_LM_b, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
3483 );
3484 update_progbar();
3485
3486 if (count_terms == 0) {
3487 k1bin_dv[idx_dv] = kbinning.bin_centres[ibin_row];
3488 k2bin_dv[idx_dv] = kbinning.bin_centres[ibin_col];
3489 k1eff_dv[idx_dv] = k_eff_a_;
3490 k2eff_dv[idx_dv] = k_eff_b_;
3491 nmodes1_dv[idx_dv] = nmodes_a_;
3492 nmodes2_dv[idx_dv] = nmodes_b_;
3493 }
3494
3495 // B_{l₁ l₂ L}^{m₁ m₂ M}
3496 double bk_comp_real = 0., bk_comp_imag = 0.;
3497
3498#ifdef TRV_USE_OMP
3499#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
3500#endif // TRV_USE_OMP
3501 for (long long gid = 0; gid < params.nmesh; gid++) {
3502 std::complex<double> F_lm_a_gridpt(
3503 F_lm_a[gid][0], F_lm_a[gid][1]
3504 );
3505 std::complex<double> F_lm_b_gridpt(
3506 F_lm_b[gid][0], F_lm_b[gid][1]
3507 );
3508 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
3509 std::complex<double> bk_gridpt =
3510 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
3511
3512 bk_comp_real += bk_gridpt.real();
3513 bk_comp_imag += bk_gridpt.imag();
3514 }
3515
3516 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
3517
3518 bk_dv[idx_dv] += coupling * vol_cell * (
3519 bk_component + factor_mirror * std::conj(bk_component)
3520 );
3521 }
3522 }
3523
3524 if (params.shape == "full") {
3525 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
3526 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
3527 int idx_dv = idx_row * params.num_bins + idx_col;
3528
3529 double k_lower_a = kbinning.bin_edges[idx_row];
3530 double k_upper_a = kbinning.bin_edges[idx_row + 1];
3531 double k_lower_b = kbinning.bin_edges[idx_col];
3532 double k_upper_b = kbinning.bin_edges[idx_col + 1];
3533
3534 double k_eff_a_, k_eff_b_;
3535 int nmodes_a_, nmodes_b_;
3536
3537 F_lm_a.inv_fourier_transform_ylm_wgtd_field_band_limited(
3538 dn_LM_a, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
3539 );
3540 F_lm_b.inv_fourier_transform_ylm_wgtd_field_band_limited(
3541 dn_LM_b, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
3542 );
3543 update_progbar();
3544
3545 if (count_terms == 0) {
3546 k1bin_dv[idx_dv] = kbinning.bin_centres[idx_row];
3547 k2bin_dv[idx_dv] = kbinning.bin_centres[idx_col];
3548 k1eff_dv[idx_dv] = k_eff_a_;
3549 k2eff_dv[idx_dv] = k_eff_b_;
3550 nmodes1_dv[idx_dv] = nmodes_a_;
3551 nmodes2_dv[idx_dv] = nmodes_b_;
3552 }
3553
3554 // B_{l₁ l₂ L}^{m₁ m₂ M}
3555 double bk_comp_real = 0., bk_comp_imag = 0.;
3556
3557#ifdef TRV_USE_OMP
3558#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
3559#endif // TRV_USE_OMP
3560 for (long long gid = 0; gid < params.nmesh; gid++) {
3561 std::complex<double> F_lm_a_gridpt(
3562 F_lm_a[gid][0], F_lm_a[gid][1]
3563 );
3564 std::complex<double> F_lm_b_gridpt(
3565 F_lm_b[gid][0], F_lm_b[gid][1]
3566 );
3567 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
3568 std::complex<double> bk_gridpt =
3569 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
3570
3571 bk_comp_real += bk_gridpt.real();
3572 bk_comp_imag += bk_gridpt.imag();
3573 }
3574
3575 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
3576
3577 bk_dv[idx_dv] += coupling * vol_cell * (
3578 bk_component + factor_mirror * std::conj(bk_component)
3579 );
3580 }
3581 }
3582 }
3583
3584 if (params.shape == "triu") {
3585 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
3586 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++) {
3587 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
3588 + (idx_col - idx_row);
3589
3590 double k_lower_a = kbinning.bin_edges[idx_row];
3591 double k_upper_a = kbinning.bin_edges[idx_row + 1];
3592 double k_lower_b = kbinning.bin_edges[idx_col];
3593 double k_upper_b = kbinning.bin_edges[idx_col + 1];
3594
3595 double k_eff_a_, k_eff_b_;
3596 int nmodes_a_, nmodes_b_;
3597
3598 F_lm_a.inv_fourier_transform_ylm_wgtd_field_band_limited(
3599 dn_LM_a, ylm_k_a, k_lower_a, k_upper_a, k_eff_a_, nmodes_a_
3600 );
3601 F_lm_b.inv_fourier_transform_ylm_wgtd_field_band_limited(
3602 dn_LM_b, ylm_k_b, k_lower_b, k_upper_b, k_eff_b_, nmodes_b_
3603 );
3604 update_progbar();
3605
3606 if (count_terms == 0) {
3607 k1bin_dv[idx_dv] = kbinning.bin_centres[idx_row];
3608 k2bin_dv[idx_dv] = kbinning.bin_centres[idx_col];
3609 k1eff_dv[idx_dv] = k_eff_a_;
3610 k2eff_dv[idx_dv] = k_eff_b_;
3611 nmodes1_dv[idx_dv] = nmodes_a_;
3612 nmodes2_dv[idx_dv] = nmodes_b_;
3613 }
3614
3615 // B_{l₁ l₂ L}^{m₁ m₂ M}
3616 double bk_comp_real = 0., bk_comp_imag = 0.;
3617
3618#ifdef TRV_USE_OMP
3619#pragma omp parallel for reduction(+:bk_comp_real, bk_comp_imag)
3620#endif // TRV_USE_OMP
3621 for (long long gid = 0; gid < params.nmesh; gid++) {
3622 std::complex<double> F_lm_a_gridpt(
3623 F_lm_a[gid][0], F_lm_a[gid][1]
3624 );
3625 std::complex<double> F_lm_b_gridpt(
3626 F_lm_b[gid][0], F_lm_b[gid][1]
3627 );
3628 std::complex<double> G_LM_gridpt(G_LM[gid][0], G_LM[gid][1]);
3629 std::complex<double> bk_gridpt =
3630 F_lm_a_gridpt * F_lm_b_gridpt * G_LM_gridpt;
3631
3632 bk_comp_real += bk_gridpt.real();
3633 bk_comp_imag += bk_gridpt.imag();
3634 }
3635
3636 std::complex<double> bk_component(bk_comp_real, bk_comp_imag);
3637
3638 bk_dv[idx_dv] += coupling * vol_cell * (
3639 bk_component + factor_mirror * std::conj(bk_component)
3640 );
3641 }
3642 }
3643 }
3644
3645 // ·······························································
3646 // Shot noise
3647 // ·······························································
3648
3649 // Compute shot noise components in eqs. (45) & (46) in the Paper.
3650 MeshField dn_LM_a_for_sn(params, true, "`dn_LM_a_for_sn`"); // δn_LM_a(k)
3651 // (for shot
3652 // noise)
3653 if (los_choice == 0) {
3654 dn_LM_a_for_sn.compute_ylm_wgtd_field(
3655 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3656 params.ELL, M_
3657 );
3658 } else {
3659 dn_LM_a_for_sn.compute_ylm_wgtd_field(
3660 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3661 0, 0
3662 );
3663 }
3664 dn_LM_a_for_sn.fourier_transform();
3665 update_progbar();
3666
3667 MeshField dn_LM_b_for_sn(params, true, "`dn_LM_b_for_sn`"); // δn_LM_b(k)
3668 // (for shot
3669 // noise)
3670 if (los_choice == 1) {
3671 dn_LM_b_for_sn.compute_ylm_wgtd_field(
3672 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3673 params.ELL, M_
3674 );
3675 } else {
3676 dn_LM_b_for_sn.compute_ylm_wgtd_field(
3677 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3678 0, 0
3679 );
3680 }
3681 dn_LM_b_for_sn.fourier_transform();
3682 update_progbar();
3683
3684 // δn_LM_c(k) (for shot noise)
3685 MeshField dn_LM_c_for_sn(params, true, "`dn_LM_c_for_sn`");
3686 if (los_choice == 2) {
3687 dn_LM_c_for_sn.compute_ylm_wgtd_field(
3688 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3689 params.ELL, M_
3690 );
3691 } else {
3692 dn_LM_c_for_sn.compute_ylm_wgtd_field(
3693 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3694 0, 0
3695 );
3696 }
3697 dn_LM_c_for_sn.fourier_transform();
3698 update_progbar();
3699
3700 MeshField N_LM_a(params, true, "`N_LM_a`"); // N_LM_a(k)
3701 if (los_choice == 0) {
3702 N_LM_a.compute_ylm_wgtd_quad_field(
3703 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3704 0, 0
3705 );
3706 } else {
3707 N_LM_a.compute_ylm_wgtd_quad_field(
3708 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3709 params.ELL, M_
3710 );
3711 }
3712 N_LM_a.fourier_transform();
3713 update_progbar();
3714
3715 MeshField N_LM_b(params, true, "`N_LM_b`"); // N_LM_b(k)
3716 if (los_choice == 1) {
3717 N_LM_b.compute_ylm_wgtd_quad_field(
3718 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3719 0, 0
3720 );
3721 } else {
3722 N_LM_b.compute_ylm_wgtd_quad_field(
3723 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3724 params.ELL, M_
3725 );
3726 }
3727 N_LM_b.fourier_transform();
3728 update_progbar();
3729
3730 MeshField N_LM_c(params, true, "`N_LM_c`"); // N_LM_c(k)
3731 if (los_choice == 2) {
3732 N_LM_c.compute_ylm_wgtd_quad_field(
3733 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3734 0, 0
3735 );
3736 } else {
3737 N_LM_c.compute_ylm_wgtd_quad_field(
3738 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3739 params.ELL, M_
3740 );
3741 }
3742 N_LM_c.fourier_transform();
3743 update_progbar();
3744
3745 std::complex<double> Sbar_LM = calc_ylm_wgtd_shotnoise_amp_for_bispec(
3746 catalogue_data, catalogue_rand, los_data, los_rand, alpha,
3747 params.ELL, M_
3748 ); // \bar{S}_LM
3749
3750 if (params.ell1 == 0 && params.ell2 == 0) {
3751 // When l₁ = l₂ = 0, the Wigner 3-j symbol enforces L = 0
3752 // and the pre-factors involving degrees and orders become 1.
3753 std::complex<double> S_ijk = coupling * Sbar_LM; // S|{i = j = k}
3754 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3755 sn_dv[idx_dv] += S_ijk;
3756 }
3757 }
3758
3759 if (params.ell2 == 0) { // S|{i ≠ j = k}
3760 // When l₂ = 0, the Wigner 3-j symbol enforces L = l₁.
3761 stats_sn.compute_ylm_wgtd_2pt_stats_in_fourier(
3762 dn_LM_a_for_sn, N_LM_a, Sbar_LM, params.ell1, m1_, kbinning
3763 );
3764
3765 if (params.shape == "diag") {
3766 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3767 int ibin = idx_dv;
3768 std::complex<double> S_i_jk = coupling * (
3769 stats_sn.pk[ibin] - stats_sn.sn[ibin]
3770 );
3771 sn_dv[idx_dv] += S_i_jk + factor_mirror * std::conj(S_i_jk);
3772 }
3773 }
3774
3775 if (params.shape == "off-diag") {
3776 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3777 int ibin_row;
3778 if (params.idx_bin >= 0) {
3779 ibin_row = idx_dv;
3780 } else {
3781 ibin_row = idx_dv + std::abs(params.idx_bin);
3782 }
3783 std::complex<double> S_i_jk = coupling * (
3784 stats_sn.pk[ibin_row] - stats_sn.sn[ibin_row]
3785 );
3786 sn_dv[idx_dv] += S_i_jk + factor_mirror * std::conj(S_i_jk);
3787 }
3788 }
3789
3790 if (params.shape == "row") {
3791 std::complex<double> S_i_jk_row_ = coupling * (
3792 stats_sn.pk[params.idx_bin] - stats_sn.sn[params.idx_bin]
3793 );
3794 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3795 sn_dv[idx_dv] +=
3796 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
3797 }
3798 }
3799
3800 if (params.shape == "full") {
3801 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
3802 std::complex<double> S_i_jk_row_ = coupling * (
3803 stats_sn.pk[idx_row] - stats_sn.sn[idx_row]
3804 );
3805 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
3806 int idx_dv = idx_row * params.num_bins + idx_col;
3807 sn_dv[idx_dv] +=
3808 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
3809 }
3810 }
3811 }
3812
3813 if (params.shape == "triu") {
3814 for (int idx_row = 0; idx_row < params.num_bins; idx_row++) {
3815 std::complex<double> S_i_jk_row_ = coupling * (
3816 stats_sn.pk[idx_row] - stats_sn.sn[idx_row]
3817 );
3818 for (int idx_col = idx_row; idx_col < params.num_bins; idx_col++)
3819 {
3820 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
3821 + (idx_col - idx_row);
3822 sn_dv[idx_dv] +=
3823 S_i_jk_row_ + factor_mirror * std::conj(S_i_jk_row_);
3824 }
3825 }
3826 }
3827 }
3828
3829 if (params.ell1 == 0) { // S|{j ≠ i = k}
3830 // When l₁ = 0, the Wigner 3-j symbol enforces L = l₂.
3831 stats_sn.compute_ylm_wgtd_2pt_stats_in_fourier(
3832 dn_LM_b_for_sn, N_LM_b, Sbar_LM,
3833 params.ell2, m2_, kbinning
3834 );
3835
3836 if (params.shape == "diag") {
3837 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3838 int ibin = idx_dv;
3839 std::complex<double> S_ik_j_ki = coupling * (
3840 stats_sn.pk[ibin] - stats_sn.sn[ibin]
3841 );
3842 sn_dv[idx_dv] +=
3843 S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
3844 }
3845 }
3846
3847 if (params.shape == "off-diag") {
3848 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3849 int ibin_col;
3850 if (params.idx_bin >= 0) {
3851 ibin_col = idx_dv + params.idx_bin;
3852 } else {
3853 ibin_col = idx_dv;
3854 }
3855 std::complex<double> S_ik_j_ki = coupling * (
3856 stats_sn.pk[ibin_col] - stats_sn.sn[ibin_col]
3857 );
3858 sn_dv[idx_dv] +=
3859 S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
3860 }
3861 }
3862
3863 if (params.shape == "row") {
3864 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3865 int ibin_col = idx_dv;
3866 std::complex<double> S_ik_j_ki = coupling * (
3867 stats_sn.pk[ibin_col] - stats_sn.sn[ibin_col]
3868 );
3869 sn_dv[idx_dv] +=
3870 S_ik_j_ki + factor_mirror * std::conj(S_ik_j_ki);
3871 }
3872 }
3873
3874 if (params.shape == "full") {
3875 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
3876 std::complex<double> S_ik_j_ki_col_ = coupling * (
3877 stats_sn.pk[idx_col] - stats_sn.sn[idx_col]
3878 );
3879 for (int idx_row = 0; idx_row <= params.num_bins; idx_row++) {
3880 int idx_dv = idx_row * params.num_bins + idx_col;
3881 sn_dv[idx_dv] +=
3882 S_ik_j_ki_col_ + factor_mirror * std::conj(S_ik_j_ki_col_);
3883 }
3884 }
3885 }
3886
3887 if (params.shape == "triu") {
3888 for (int idx_col = 0; idx_col < params.num_bins; idx_col++) {
3889 std::complex<double> S_ik_j_ki_col_ = coupling * (
3890 stats_sn.pk[idx_col] - stats_sn.sn[idx_col]
3891 );
3892 for (int idx_row = 0; idx_row <= idx_col; idx_row++) {
3893 int idx_dv = (2*params.num_bins - idx_row + 1) * idx_row / 2
3894 + (idx_col - idx_row);
3895 sn_dv[idx_dv] +=
3896 S_ik_j_ki_col_ + factor_mirror * std::conj(S_ik_j_ki_col_);
3897 }
3898 }
3899 }
3900 }
3901
3902 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3903 double k_a = k1eff_dv[idx_dv];
3904 double k_b = k2eff_dv[idx_dv];
3905
3906 std::complex<double> S_ij_k = coupling
3907 * stats_sn.compute_uncoupled_shotnoise_for_bispec_per_bin(
3908 dn_LM_c_for_sn, N_LM_c, ylm_r_a, ylm_r_b, sj_a, sj_b,
3909 Sbar_LM, k_a, k_b
3910 ); // S|{i = j ≠ k}
3911 update_progbar();
3912
3913 sn_dv[idx_dv] += factor_phase * (
3914 S_ij_k + factor_mirror_w3j * std::conj(S_ij_k)
3915 );
3916 }
3917
3918 sph_orders_computed.push_back(sph_order_current);
3919 count_terms++;
3920 if (trvs::currTask == 0) {
3922 "Bispectrum term computed at orders (m₁, m₂, M) = ±(%d, %d, %d).",
3923 m1_, m2_, M_
3924 );
3925 }
3926 }
3927 }
3928 }
3929
3930 // ---------------------------------------------------------------------
3931 // Results
3932 // ---------------------------------------------------------------------
3933
3934 trv::BispecMeasurements bispec_out;
3935 for (int idx_dv = 0; idx_dv < dv_dim; idx_dv++) {
3936 bispec_out.k1_bin.push_back(k1bin_dv[idx_dv]);
3937 bispec_out.k1_eff.push_back(k1eff_dv[idx_dv]);
3938 bispec_out.nmodes_1.push_back(nmodes1_dv[idx_dv]);
3939 bispec_out.k2_bin.push_back(k2bin_dv[idx_dv]);
3940 bispec_out.k2_eff.push_back(k2eff_dv[idx_dv]);
3941 bispec_out.nmodes_2.push_back(nmodes2_dv[idx_dv]);
3942 bispec_out.bk_raw.push_back(norm_factor * bk_dv[idx_dv]);
3943 bispec_out.bk_shot.push_back(norm_factor * sn_dv[idx_dv]);
3944 }
3945 bispec_out.dim = dv_dim;
3946
3947 delete[] nmodes1_dv; delete[] nmodes2_dv;
3948 delete[] k1bin_dv; delete[] k2bin_dv;
3949 delete[] k1eff_dv; delete[] k2eff_dv;
3950 delete[] bk_dv; delete[] sn_dv;
3951
3952 trvs::count_cgrid -= 4;
3953 trvs::count_grid -= 4;
3956
3957 if (trvs::currTask == 0) {
3959 "... computed bispectrum from paired survey-type catalogues "
3960 "for line-of-sight choice %d.",
3961 los_choice
3962 );
3963 }
3964
3965 return bispec_out;
3966}
3967#endif // TRV_USE_LEGACY_CODE
3968
3969} // namespace trv
Isotropic coordinate binning.
Definition dataobjs.hpp:58
std::vector< double > bin_edges
bin edges
Definition dataobjs.hpp:65
std::vector< double > bin_centres
bin centres
Definition dataobjs.hpp:66
int num_bins
number of bins
Definition dataobjs.hpp:64
Field (pseudo-two-point) statistics.
Definition field.hpp:582
std::vector< std::complex< double > > sn
shot-noise power in bins
Definition field.hpp:589
std::vector< std::complex< double > > pk
pseudo power spectrum in bins
Definition field.hpp:591
std::vector< double > r
Definition field.hpp:587
void compute_uncoupled_shotnoise_for_3pcf(MeshField &field_a, MeshField &field_b, std::vector< std::complex< double > > &ylm_a, std::vector< std::complex< double > > &ylm_b, std::complex< double > shotnoise_amp, trv::Binning &rbinning)
Compute binned uncoupled three-point correlation function shot noise.
Definition field.cpp:2947
std::complex< double > compute_uncoupled_shotnoise_for_bispec_per_bin(MeshField &field_a, MeshField &field_b, std::vector< std::complex< double > > &ylm_a, std::vector< std::complex< double > > &ylm_b, trvm::SphericalBesselCalculator &sj_a, trvm::SphericalBesselCalculator &sj_b, std::complex< double > shotnoise_amp, double k_a, double k_b)
Compute unbinned uncoupled bispectrum shot noise on the FFT mesh grid.
Definition field.cpp:3199
std::vector< std::complex< double > > xi
pseudo two-point correlation function in bins
Definition field.hpp:593
std::vector< int > npairs
number of separation pairs in bins
Definition field.hpp:585
void compute_ylm_wgtd_2pt_stats_in_fourier(MeshField &field_a, MeshField &field_b, std::complex< double > shotnoise_amp, int ell, int m, trv::Binning &kbinning)
Compute binned two-point statistics in Fourier space.
Definition field.cpp:2511
Discretely sampled field on a mesh grid from particle catalogues.
Definition field.hpp:83
void inv_fourier_transform()
Inverse Fourier transform the (FFT-transformed) field.
Definition field.cpp:1657
void inv_fourier_transform_sjl_ylm_wgtd_field(MeshField &field_fourier, std::vector< std::complex< double > > &ylm, trvm::SphericalBesselCalculator &sjl, double r)
Inverse Fourier transform a field weighted by the spherical Bessel function and reduced spherical ha...
Definition field.cpp:1908
double calc_grid_based_powlaw_norm(ParticleCatalogue &particles, int order)
Definition field.cpp:2017
void inv_fourier_transform_ylm_wgtd_field_band_limited(MeshField &field_fourier, std::vector< std::complex< double > > &ylm, double k_band, double dk_band, double &k_eff, int &nmodes)
Inverse Fourier transform a field weighted by the reduced spherical harmonics restricted to a wavenu...
Definition field.cpp:1792
void fourier_transform()
Fourier transform the field.
Definition field.cpp:1496
void compute_ylm_wgtd_field(ParticleCatalogue &particles_data, ParticleCatalogue &particles_rand, LineOfSight *los_data, LineOfSight *los_rand, double alpha, int ell, int m)
Compute the weighted field (fluctuations) further weighted by the reduced spherical harmonics.
Definition field.cpp:1246
void compute_unweighted_field(ParticleCatalogue &particles)
Compute the unweighted field.
Definition field.cpp:1208
void apply_wide_angle_pow_law_kernel()
Apply wide-angle correction kernel in configuration space.
Definition field.cpp:1727
void compute_ylm_wgtd_quad_field(ParticleCatalogue &particles_data, ParticleCatalogue &particles_rand, LineOfSight *los_data, LineOfSight *los_rand, double alpha, int ell, int m)
Compute the quadratic weighted field (fluctuations) further weighted by the reduced spherical harmoni...
Definition field.cpp:1364
void compute_unweighted_field_fluctuations_insitu(ParticleCatalogue &particles)
Compute the unweighted field fluctuations.
Definition field.cpp:1229
double vol_cell
mesh grid cell volume
Definition field.hpp:91
void apply_assignment_compensation()
Apply compensation needed after Fourier transform for assignment schemes.
Definition field.cpp:1764
Parameter set.
std::string shape
long long nmesh
number of mesh grid cells
int ELL
spherical degree associated with the line of sight
int ell1
spherical degree associated with the first wavevector
int num_bins
number of measurement bins
int ell2
spherical degree associated with the second wavevector
int ngrid[3]
grid cell number in each dimension
std::string progbar
display a progress bar: {"true", "false" (default), <int-%-point>}
int idx_bin
fixed bin index in "off-fiag"/"row" form three-point measurements
double boxsize[3]
box size (in Mpc/h) in each dimension
Particle catalogue.
Definition particles.hpp:78
ParticleData * pdata
particle data
Definition particles.hpp:82
int ntotal
total number of particles
Definition particles.hpp:84
double wstotal
total sample weight of particles
Definition particles.hpp:86
Interpolated spherical Bessel function of the first kind.
Definition maths.hpp:268
static void store_reduced_spherical_harmonic_in_config_space(const int ell, const int m, const double boxsize[3], const int ngrid[3], std::vector< std::complex< double > > &ylm_out)
Store reduced spherical harmonics computed in configuration space.
Definition maths.cpp:263
static void store_reduced_spherical_harmonic_in_fourier_space(const int ell, const int m, const double boxsize[3], const int ngrid[3], std::vector< std::complex< double > > &ylm_out)
Store reduced spherical harmonics computed in Fourier space.
Definition maths.cpp:223
static std::complex< double > calc_reduced_spherical_harmonic(const int ell, const int m, double pos[3])
Calculate the reduced spherical harmonic.
Definition maths.cpp:172
Exception raised when the data to be operated on are invalid.
Definition monitor.hpp:755
Exception raised when parameters are invalid.
Definition monitor.hpp:731
void error(const char *fmt_string,...)
Emit a warning-level message.
Definition monitor.cpp:493
void stat(const char *fmt_string,...)
Emit a status-level message.
Definition monitor.cpp:454
Progress bar for tracking tasks.
Definition monitor.hpp:583
void set_nodes(std::vector< float > nodes)
Set progress bar width.
Definition monitor.cpp:544
void update(int task_idx_now)
Update the progress bar.
Definition monitor.cpp:580
const std::complex< double > M_I
imaginary unit
const double eps_coupling
zero-tolerance for Wigner 3-j coupling coefficients
Definition maths.cpp:165
double wigner_3j(int j1, int j2, int j3, int m1, int m2, int m3)
Calculate Wigner 3-j symbol.
Definition maths.cpp:167
double gbytesMem
current memory usage in gibibytes
Definition monitor.cpp:94
void update_maxmem(bool gpu=false)
Update the maximum memory usage estimate.
Definition monitor.cpp:165
double size_in_gb(long long num)
Return size in gibibytes.
Definition monitor.hpp:281
bool is_gpu_enabled()
Check if GPU mode is enabled.
Definition monitor.cpp:297
Logger logger
default logger (at NSET logging level)
float count_grid
number of grids
Definition monitor.cpp:102
int currTask
current task
Definition monitor.cpp:92
std::vector< float > set_nodes_by_str(std::string interval_str)
Set a node list possibly from a string.
Definition monitor.cpp:649
int count_fft
number of FFTs
Definition monitor.cpp:107
int count_cgrid
number of 3-d complex grids
Definition monitor.cpp:101
int count_ifft
number of IFFTs
Definition monitor.cpp:108
void update_maxcntgrid()
Update the maximum 3-d grid counts.
Definition monitor.cpp:177
double calc_coupling_coeff_3pt(int ell1, int ell2, int ELL, int m1, int m2, int M)
Calculate the coupling coefficient for spherical-harmonic components of full three-point statistics.
Definition threept.cpp:65
double calc_bispec_normalisation_from_particles(ParticleCatalogue &particles, double alpha=1.)
Calculate particle-based bispectrum normalisation.
Definition threept.cpp:96
std::complex< double > calc_ylm_wgtd_shotnoise_amp_for_bispec(ParticleCatalogue &particles_data, ParticleCatalogue &particles_rand, LineOfSight *los_data, LineOfSight *los_rand, double alpha, int ell, int m)
Calculate bispectrum shot noise amplitude weighted by reduced spherical harmonics.
Definition threept.cpp:156
trv::ThreePCFMeasurements compute_3pcf_in_gpp_box(ParticleCatalogue &catalogue_data, trv::ParameterSet &params, trv::Binning &rbinning, double norm_factor)
Compute three-point correlation function in a periodic box in the global plane-parallel approximation...
Definition threept.cpp:2190
trv::BispecMeasurements compute_bispec_in_gpp_box(ParticleCatalogue &catalogue_data, trv::ParameterSet &params, trv::Binning kbinning, double norm_factor)
Compute bispectrum in a periodic box in the global plane-parallel approximation.
Definition threept.cpp:1473
trv::ThreePCFWindowMeasurements compute_3pcf_window(ParticleCatalogue &catalogue_rand, LineOfSight *los_rand, trv::ParameterSet &params, trv::Binning &rbinning, double alpha, double norm_factor, bool wide_angle=false)
Compute three-point correlation function window from a random catalogue.
Definition threept.cpp:2621
double calc_bispec_normalisation_from_mesh(ParticleCatalogue &particles, trv::ParameterSet &params, double alpha=1.)
Calculate mesh-based bispectrum normalisation.
Definition threept.cpp:138
trv::BispecMeasurements compute_bispec(ParticleCatalogue &catalogue_data, ParticleCatalogue &catalogue_rand, LineOfSight *los_data, LineOfSight *los_rand, trv::ParameterSet &params, trv::Binning &kbinning, double norm_factor)
Compute bispectrum from paired survey-type catalogues.
Definition threept.cpp:248
void validate_multipole_coupling(trv::ParameterSet &params)
Validate three-point correlator multipoles are non-vanishing.
Definition threept.cpp:73
trv::ThreePCFMeasurements compute_3pcf(ParticleCatalogue &catalogue_data, ParticleCatalogue &catalogue_rand, LineOfSight *los_data, LineOfSight *los_rand, trv::ParameterSet &params, trv::Binning &rbinning, double norm_factor)
Compute three-point correlation function from paired survey-type catalogues.
Definition threept.cpp:1014
Bispectrum measurements.
Definition dataobjs.hpp:249
std::vector< std::complex< double > > bk_raw
bispectrum raw measurements (with normalisation and shot noise)
Definition dataobjs.hpp:258
std::vector< double > k2_eff
second effective wavenumber in bins
Definition dataobjs.hpp:254
std::vector< int > nmodes_2
Definition dataobjs.hpp:256
std::vector< std::complex< double > > bk_shot
bispectrum shot noise
Definition dataobjs.hpp:260
std::vector< double > k1_bin
first central wavenumber in bins
Definition dataobjs.hpp:251
std::vector< double > k1_eff
first effective wavenumber in bins
Definition dataobjs.hpp:253
std::vector< double > k2_bin
second central wavenumber in bins
Definition dataobjs.hpp:252
int dim
dimension of data vector
Definition dataobjs.hpp:250
std::vector< int > nmodes_1
number of first wavevectors in bins
Definition dataobjs.hpp:255
Line-of-sight vector.
Definition dataobjs.hpp:186
double pos[3]
3-d position vector
Definition dataobjs.hpp:187
Spherical order triplet .
Definition threept.hpp:74
bool is_zeros()
Check if the spherical orders are all zeros.
Definition threept.cpp:49
SphericalOrderTriplet operator+(const SphericalOrderTriplet &other)
Return the sum with another spherical order triplet.
Definition threept.cpp:40
int M
spherical orders
Definition threept.hpp:75
bool is_inverse(const SphericalOrderTriplet &other)
Check if another triplet is the additive inverse.
Definition threept.cpp:53
Three-point correlation function measurements.
Definition dataobjs.hpp:267
std::vector< double > r2_bin
second central separation in bins
Definition dataobjs.hpp:270
std::vector< int > npairs_1
number of first separation vectors in bins
Definition dataobjs.hpp:274
std::vector< double > r1_bin
first central separation in bins
Definition dataobjs.hpp:269
std::vector< std::complex< double > > zeta_shot
three-point correlation function shot noise
Definition dataobjs.hpp:281
std::vector< double > r2_eff
Definition dataobjs.hpp:272
std::vector< std::complex< double > > zeta_raw
Definition dataobjs.hpp:279
std::vector< double > r1_eff
first effective separation in bins
Definition dataobjs.hpp:271
std::vector< int > npairs_2
number of second separation vectors in bins
Definition dataobjs.hpp:276
int dim
dimension of data vector
Definition dataobjs.hpp:268
Three-point correlation function window measurements.
Definition dataobjs.hpp:288
std::vector< std::complex< double > > zeta_shot
three-point correlation function window shot noise
Definition dataobjs.hpp:302
std::vector< int > npairs_2
number of second separation vectors in bins
Definition dataobjs.hpp:297
std::vector< int > npairs_1
number of first separation vectors in bins
Definition dataobjs.hpp:295
std::vector< double > r1_eff
first effective separation in bins
Definition dataobjs.hpp:292
std::vector< double > r2_eff
Definition dataobjs.hpp:293
int dim
dimension of data vector
Definition dataobjs.hpp:289
std::vector< double > r2_bin
second central separation in bins
Definition dataobjs.hpp:291
std::vector< double > r1_bin
first central separation in bins
Definition dataobjs.hpp:290
std::vector< std::complex< double > > zeta_raw
Definition dataobjs.hpp:300
Three-point statistic computations.