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