Triumvirate C++ API 0.5.0
Three-point clustering measurements in large-scale structure analyses.
Loading...
Searching...
No Matches
field.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 "field.hpp"
28
29namespace trvs = trv::sys;
30namespace trvm = trv::maths;
31
32namespace trv {
33
34// ***********************************************************************
35// Mesh field
36// ***********************************************************************
37
38// -----------------------------------------------------------------------
39// Life cycle
40// -----------------------------------------------------------------------
41
43 trv::ParameterSet& params, bool plan_ini, const std::string& name
44) {
45 // Attach the full parameter set to @ref trv::MeshField.
46 this->params = params;
47 this->name = name;
48
50
51 // Initialise the field (and its shadow field if interlacing is used)
52 // and increase allocated memory.
53 this->field = fftw_alloc_complex(this->params.nmesh);
54
60
61 if (this->params.interlace == "true") {
62 this->field_s = fftw_alloc_complex(this->params.nmesh);
63
69 }
70
71 this->reset_density_field(); // initialise; likely redundant but safe
72
73 // Initialise FFTW plans.
74 if (plan_ini) {
75#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
76 fftw_plan_with_nthreads(omp_get_max_threads());
77#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
78 bool import_fftw_wisdom_f = false;
79 bool import_fftw_wisdom_b = false;
80 bool export_fftw_wisdom_f = false;
81 bool export_fftw_wisdom_b = false;
82 std::FILE* fftw_wisdom_file_f = nullptr;
83 std::FILE* fftw_wisdom_file_b = nullptr;
84
85 if (this->params.use_fftw_wisdom != "") {
87 fftw_wisdom_file_f =
88 std::fopen(this->params.fftw_wisdom_file_f.c_str(), "r");
89 if (fftw_wisdom_file_f != nullptr) {
90 import_fftw_wisdom_f = true;
91 export_fftw_wisdom_f = false;
92 if (this->params.fftw_scheme == "patient") {
93 if (trvs::currTask == 0) {
95 "FFTW planner flag is set to `FFTW_PATIENT`. "
96 "Ensure that the FFTW wisdom file '%s' imported has been "
97 "generated with an equivalent or higher planner flag.",
98 this->params.fftw_wisdom_file_f.c_str()
99 );
100 }
101 }
102 } else {
103 import_fftw_wisdom_f = false;
104 export_fftw_wisdom_f = true;
105 if (trvs::currTask == 0) {
107 "No FFTW wisdom file for forward transforms "
108 "could be imported: %s",
109 this->params.fftw_wisdom_file_f.c_str()
110 );
111 }
112 }
113 }
114
116 fftw_wisdom_file_b =
117 fopen(this->params.fftw_wisdom_file_b.c_str(), "r");
118 if (fftw_wisdom_file_b != nullptr) {
119 import_fftw_wisdom_b = true;
120 export_fftw_wisdom_b = false;
121 if (this->params.fftw_scheme == "patient") {
122 if (trvs::currTask == 0) {
124 "FFTW planner flag is set to `FFTW_PATIENT`. "
125 "Ensure that the FFTW wisdom file '%s' imported has been "
126 "generated with an equivalent or higher planner flag.",
127 this->params.fftw_wisdom_file_b.c_str()
128 );
129 }
130 }
131 } else {
132 import_fftw_wisdom_b = false;
133 export_fftw_wisdom_b = true;
134 if (trvs::currTask == 0) {
136 "No FFTW wisdom file for backward transforms "
137 "could be imported: %s",
138 this->params.fftw_wisdom_file_b.c_str()
139 );
140 }
141 }
142 }
143 }
144
145 if (import_fftw_wisdom_f) {
146 fftw_import_wisdom_from_filename(
147 this->params.fftw_wisdom_file_f.c_str()
148 );
150 if (trvs::currTask == 0) {
152 "FFTW wisdom file for forward transforms has been imported: %s",
153 this->params.fftw_wisdom_file_f.c_str()
154 );
155 }
156 }
157
158 auto pre_plan_f_timept = std::chrono::system_clock::now();
159 this->transform = fftw_plan_dft_3d(
160 this->params.ngrid[0], this->params.ngrid[1], this->params.ngrid[2],
161 this->field, this->field,
162 FFTW_FORWARD, this->params.fftw_planner_flag
163 );
164 auto post_plan_f_timept = std::chrono::system_clock::now();
165
166 double plan_f_time = std::chrono::duration<double>(
167 post_plan_f_timept - pre_plan_f_timept
168 ).count();
169 if (import_fftw_wisdom_f && plan_f_time > 1.) {
170 if (trvs::currTask == 0) {
172 "FFTW plan for forward transforms took %.3f (> 1.) seconds "
173 "despite importing wisdom file, which may have been created "
174 "under different runtime conditions. "
175 "A new wisdom file will be exported.",
176 plan_f_time
177 );
178 }
179 export_fftw_wisdom_f = true;
180 }
181
182 if (export_fftw_wisdom_f) {
183 fftw_export_wisdom_to_filename(
184 this->params.fftw_wisdom_file_f.c_str()
185 );
186 if (trvs::currTask == 0) {
188 "FFTW wisdom file for forward transforms has been exported: %s",
189 this->params.fftw_wisdom_file_f.c_str()
190 );
191 }
193 }
194
195 if (import_fftw_wisdom_b) {
196 fftw_import_wisdom_from_filename(
197 this->params.fftw_wisdom_file_b.c_str()
198 );
200 if (trvs::currTask == 0) {
202 "FFTW wisdom file for backward transforms has been imported: %s",
203 this->params.fftw_wisdom_file_b.c_str()
204 );
205 }
206 }
207
208 auto pre_plan_b_timept = std::chrono::system_clock::now();
209 this->inv_transform = fftw_plan_dft_3d(
210 this->params.ngrid[0], this->params.ngrid[1], this->params.ngrid[2],
211 this->field, this->field,
212 FFTW_BACKWARD, this->params.fftw_planner_flag
213 );
214 auto post_plan_b_timept = std::chrono::system_clock::now();
215
216 double plan_b_time = std::chrono::duration<double>(
217 post_plan_b_timept - pre_plan_b_timept
218 ).count();
219 if (import_fftw_wisdom_b && plan_b_time > 1.) {
220 if (trvs::currTask == 0) {
222 "FFTW plan for backward transforms took %.3f (> 1.) seconds "
223 "despite importing wisdom file, which may have been created "
224 "under different runtime conditions. "
225 "A new wisdom file will be exported.",
226 plan_b_time
227 );
228 }
229 export_fftw_wisdom_b = true;
230 }
231
232 if (export_fftw_wisdom_b) {
233 fftw_export_wisdom_to_filename(
234 this->params.fftw_wisdom_file_b.c_str()
235 );
236 if (trvs::currTask == 0) {
238 "FFTW wisdom file for backward transforms has been exported: %s",
239 this->params.fftw_wisdom_file_b.c_str()
240 );
241 }
243 }
244
245 if (this->params.interlace == "true") {
246 this->transform_s = fftw_plan_dft_3d(
247 this->params.ngrid[0], this->params.ngrid[1], this->params.ngrid[2],
248 this->field_s, this->field_s,
249 FFTW_FORWARD, this->params.fftw_planner_flag
250 );
251 }
252 this->plan_ini = true;
253
254 if (fftw_wisdom_file_f != nullptr) {std::fclose(fftw_wisdom_file_f);}
255 if (fftw_wisdom_file_b != nullptr) {std::fclose(fftw_wisdom_file_b);}
256 }
257
258 // Calculate grid sizes in configuration space.
259 this->dr[0] = this->params.boxsize[0] / this->params.ngrid[0];
260 this->dr[1] = this->params.boxsize[1] / this->params.ngrid[1];
261 this->dr[2] = this->params.boxsize[2] / this->params.ngrid[2];
262
263 // Calculate fundamental wavenumbers in Fourier space.
264 this->dk[0] = 2.*M_PI / this->params.boxsize[0];
265 this->dk[1] = 2.*M_PI / this->params.boxsize[1];
266 this->dk[2] = 2.*M_PI / this->params.boxsize[2];
267
268 // Calculate mesh volume and mesh grid cell volume.
269 this->vol = this->params.volume;
270 this->vol_cell = this->vol / double(this->params.nmesh);
271}
272
274 trv::ParameterSet& params,
275 fftw_plan& transform, fftw_plan& inv_transform,
276 const std::string& name
277) {
278 // Attach the full parameter set to @ref trv::MeshField.
279 this->params = params;
280 this->name = name;
281
283
284 // Initialise the field (and its shadow field if interlacing is used)
285 // and increase allocated memory.
286 this->field = fftw_alloc_complex(this->params.nmesh);
287
289 trvs::count_grid += 1;
293
294 if (this->params.interlace == "true") {
295 this->field_s = fftw_alloc_complex(this->params.nmesh);
296
298 trvs::count_grid += 1;
302 }
303
304 this->reset_density_field(); // initialise; likely redundant but safe
305
306 // Initialise FFTW plans.
307 this->transform = transform;
308 this->inv_transform = inv_transform;
309 if (this->params.interlace == "true") {
310 this->transform_s = transform;
311 }
312 this->plan_ext = true;
313
314 // Calculate grid sizes in configuration space.
315 this->dr[0] = this->params.boxsize[0] / this->params.ngrid[0];
316 this->dr[1] = this->params.boxsize[1] / this->params.ngrid[1];
317 this->dr[2] = this->params.boxsize[2] / this->params.ngrid[2];
318
319 // Calculate fundamental wavenumbers in Fourier space.
320 this->dk[0] = 2.*M_PI / this->params.boxsize[0];
321 this->dk[1] = 2.*M_PI / this->params.boxsize[1];
322 this->dk[2] = 2.*M_PI / this->params.boxsize[2];
323
324 // Calculate mesh volume and mesh grid cell volume.
325 this->vol = this->params.volume;
326 this->vol_cell = this->vol / double(this->params.nmesh);
327}
328
330 if (this->plan_ini) {
331 fftw_destroy_plan(this->transform);
332 fftw_destroy_plan(this->inv_transform);
333 if (this->params.interlace == "true") {
334 fftw_destroy_plan(this->transform_s);
335 }
336 }
337
338 if (this->window_assign_order != -1) {
340 trvs::count_grid -= .5;
342 }
343
344 if (this->field != nullptr) {
345 fftw_free(this->field); this->field = nullptr;
347 trvs::count_grid -= 1;
349 }
350 if (this->field_s != nullptr) {
351 fftw_free(this->field_s); this->field_s = nullptr;
353 trvs::count_grid -= 1;
355 }
356}
357
359#ifdef TRV_USE_OMP
360#pragma omp parallel for simd
361#endif // TRV_USE_OMP
362 for (long long gid = 0; gid < this->params.nmesh; gid++) {
363 this->field[gid][0] = 0.;
364 this->field[gid][1] = 0.;
365 }
366 if (this->params.interlace == "true") {
367#ifdef TRV_USE_OMP
368#pragma omp parallel for simd
369#endif // TRV_USE_OMP
370 for (long long gid = 0; gid < this->params.nmesh; gid++) {
371 this->field_s[gid][0] = 0.;
372 this->field_s[gid][1] = 0.;
373 }
374 }
375}
376
377
378// -----------------------------------------------------------------------
379// Operators & reserved methods
380// -----------------------------------------------------------------------
381
382const fftw_complex& MeshField::operator[](long long gid) {
383 return this->field[gid];
384}
385
386
387// -----------------------------------------------------------------------
388// Mesh grid properties
389// -----------------------------------------------------------------------
390
391long long MeshField::ret_grid_index(int i, int j, int k) {
392 long long idx_grid =
393 (i * static_cast<long long>(this->params.ngrid[1]) + j)
394 * this->params.ngrid[2] + k;
395 return idx_grid;
396}
397
398void MeshField::shift_grid_indices_fourier(int& i, int& j, int& k) {
399 i = (i < this->params.ngrid[0]/2) ? i : i - this->params.ngrid[0];
400 j = (j < this->params.ngrid[1]/2) ? j : j - this->params.ngrid[1];
401 k = (k < this->params.ngrid[2]/2) ? k : k - this->params.ngrid[2];
402}
403
404void MeshField::get_grid_pos_vector(int i, int j, int k, double rvec[3]) {
405 rvec[0] = (i < this->params.ngrid[0]/2) ?
406 i * this->dr[0] : (i - this->params.ngrid[0]) * this->dr[0];
407 rvec[1] = (j < this->params.ngrid[1]/2) ?
408 j * this->dr[1] : (j - this->params.ngrid[1]) * this->dr[1];
409 rvec[2] = (k < this->params.ngrid[2]/2) ?
410 k * this->dr[2] : (k - this->params.ngrid[2]) * this->dr[2];
411}
412
413void MeshField::get_grid_wavevector(int i, int j, int k, double kvec[3]) {
414 kvec[0] = (i < this->params.ngrid[0]/2) ?
415 i * this->dk[0] : (i - this->params.ngrid[0]) * this->dk[0];
416 kvec[1] = (j < this->params.ngrid[1]/2) ?
417 j * this->dk[1] : (j - this->params.ngrid[1]) * this->dk[1];
418 kvec[2] = (k < this->params.ngrid[2]/2) ?
419 k * this->dk[2] : (k - this->params.ngrid[2]) * this->dk[2];
420}
421
422
423// -----------------------------------------------------------------------
424// Mesh assignment
425// -----------------------------------------------------------------------
426
428 ParticleCatalogue& particles, fftw_complex* weights
429) {
430 if (trvs::currTask == 0) {
432 "Performing mesh assignment scheme '%s' to '%s'.",
433 this->params.assignment.c_str(),
434 this->name.c_str()
435 );
436 }
437
438 for (int iaxis = 0; iaxis < 3; iaxis++) {
439 double extent = particles.pos_max[iaxis] - particles.pos_min[iaxis];
440 if (params.boxsize[iaxis] < extent) {
441 if (trvs::currTask == 0) {
443 "Box size in dimension %d is smaller than catalogue extents: "
444 "%.3f < %.3f.",
445 iaxis, params.boxsize[iaxis], extent
446 );
447 }
448 }
449 }
450
451 if (this->params.assignment == "ngp") {
452 this->assign_weighted_field_to_mesh_ngp(particles, weights);
453 } else
454 if (this->params.assignment == "cic") {
455 this->assign_weighted_field_to_mesh_cic(particles, weights);
456 } else
457 if (this->params.assignment == "tsc") {
458 this->assign_weighted_field_to_mesh_tsc(particles, weights);
459 } else
460 if (this->params.assignment == "pcs") {
461 this->assign_weighted_field_to_mesh_pcs(particles, weights);
462 } else {
463 if (trvs::currTask == 0) {
465 "Unsupported mesh assignment scheme: '%s'.",
466 this->params.assignment.c_str()
467 );
468 };
470 "Unsupported mesh assignment scheme: '%s'.\n",
471 this->params.assignment.c_str()
472 );
473 }
474}
475
476void MeshField::assign_weighted_field_to_mesh_ngp(
477 ParticleCatalogue& particles, fftw_complex* weight
478) {
479 // Set interpolation order, i.e. number of grids, per dimension,
480 // to which a single particle is assigned.
481 const int order = 1;
482
483 // Here the field is given by Σᵢ wᵢ δᴰ(x - xᵢ),
484 // where δᴰ ↔ δᴷ / dV, dV =: `vol_cell`.
485 const double inv_vol_cell = 1 / this->vol_cell;
486
487 // Reset field values to zero.
488 this->reset_density_field();
489
490 // Perform assignment.
491#ifdef TRV_USE_OMP
492#pragma omp parallel for
493#endif // TRV_USE_OMP
494 for (int pid = 0; pid < particles.ntotal; pid++) {
495 int ijk[order][3]; // grid index coordinates of covered grid cells
496 double win[order][3]; // sampling window
497 long long gid = 0; // flattened grid cell index
498
499 for (int iaxis = 0; iaxis < 3; iaxis++) {
500 // Carefully set covered sampling window grid indices.
501 double loc_grid = this->params.ngrid[iaxis]
502 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis];
503
504 int idx_grid = int(loc_grid);
505 if (loc_grid - idx_grid >= 0.5) {
506 idx_grid = (idx_grid == this->params.ngrid[iaxis] - 1)
507 ? 0 : idx_grid + 1;
508 }
509
510 ijk[0][iaxis] = idx_grid;
511
512 // Set sampling window value (only 0th element as ``order == 1``).
513 win[0][iaxis] = 1.;
514 }
515
516 for (int iloc = 0; iloc < order; iloc++) {
517 for (int jloc = 0; jloc < order; jloc++) {
518 for (int kloc = 0; kloc < order; kloc++) {
519 gid = this->ret_grid_index(
520 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
521 );
522 if (0 <= gid && gid < this->params.nmesh) {
523OMP_ATOMIC
524 this->field[gid][0] += inv_vol_cell
525 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
526OMP_ATOMIC
527 this->field[gid][1] += inv_vol_cell
528 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
529 }
530 }
531 }
532 }
533 }
534
535 // Perform interlacing if needed.
536 if (this->params.interlace == "true") {
537#ifdef TRV_USE_OMP
538#pragma omp parallel for
539#endif // TRV_USE_OMP
540 for (int pid = 0; pid < particles.ntotal; pid++) {
541 int ijk[order][3];
542 double win[order][3];
543 long long gid = 0;
544
545 for (int iaxis = 0; iaxis < 3; iaxis++) {
546 // Apply a half-grid shift and impose the periodic boundary condition.
547 double loc_grid = this->params.ngrid[iaxis]
548 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis] + 0.5;
549
550 if (loc_grid > this->params.ngrid[iaxis]) {
551 loc_grid -= this->params.ngrid[iaxis];
552 }
553
554 int idx_grid = int(loc_grid);
555 if (loc_grid - idx_grid >= 0.5) {
556 idx_grid = (idx_grid == this->params.ngrid[iaxis] - 1)
557 ? 0 : idx_grid + 1;
558 }
559
560 ijk[0][iaxis] = idx_grid;
561
562 win[0][iaxis] = 1.;
563 }
564
565 for (int iloc = 0; iloc < order; iloc++) {
566 for (int jloc = 0; jloc < order; jloc++) {
567 for (int kloc = 0; kloc < order; kloc++) {
568 gid = this->ret_grid_index(
569 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
570 );
571 if (0 <= gid && gid < this->params.nmesh) {
572OMP_ATOMIC
573 this->field_s[gid][0] += inv_vol_cell
574 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
575OMP_ATOMIC
576 this->field_s[gid][1] += inv_vol_cell
577 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
578 }
579 }
580 }
581 }
582 }
583 }
584}
585
586void MeshField::assign_weighted_field_to_mesh_cic(
587 ParticleCatalogue& particles, fftw_complex* weight
588) {
589 // Set interpolation order, i.e. number of grids, per dimension,
590 // to which a single particle is assigned.
591 const int order = 2;
592
593 // Here the field is given by Σᵢ wᵢ δᴰ(x - xᵢ),
594 // where δᴰ ↔ δᴷ / dV, dV =: `vol_cell`.
595 const double inv_vol_cell = 1 / this->vol_cell;
596
597 // Reset field values to zero.
598 this->reset_density_field();
599
600 // Perform assignment.
601#ifdef TRV_USE_OMP
602#pragma omp parallel for
603#endif // TRV_USE_OMP
604 for (int pid = 0; pid < particles.ntotal; pid++) {
605 int ijk[order][3]; // grid index coordinates of covered grid cells
606 double win[order][3]; // sampling window
607 long long gid = 0; // flattened grid cell index
608
609 for (int iaxis = 0; iaxis < 3; iaxis++) {
610 // Carefully set covered sampling window grid indices.
611 double loc_grid = this->params.ngrid[iaxis]
612 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis];
613
614 int idx_grid = int(loc_grid);
615
616 ijk[0][iaxis] = idx_grid;
617 ijk[1][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
618 ? 0 : idx_grid + 1;
619
620 // Set sampling window value (up to the 1st element as `order == 2`).
621 double s = loc_grid - idx_grid; // particle-to-grid grid-index distance
622
623 win[0][iaxis] = 1. - s;
624 win[1][iaxis] = s;
625 }
626
627 for (int iloc = 0; iloc < order; iloc++) {
628 for (int jloc = 0; jloc < order; jloc++) {
629 for (int kloc = 0; kloc < order; kloc++) {
630 gid = this->ret_grid_index(
631 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
632 );
633 if (0 <= gid && gid < this->params.nmesh) {
634OMP_ATOMIC
635 this->field[gid][0] += inv_vol_cell
636 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
637OMP_ATOMIC
638 this->field[gid][1] += inv_vol_cell
639 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
640 }
641 }
642 }
643 }
644 }
645
646 // Perform interlacing if needed.
647 if (this->params.interlace == "true") {
648#ifdef TRV_USE_OMP
649#pragma omp parallel for
650#endif // TRV_USE_OMP
651 for (int pid = 0; pid < particles.ntotal; pid++) {
652 int ijk[order][3];
653 double win[order][3];
654 long long gid = 0;
655
656 for (int iaxis = 0; iaxis < 3; iaxis++) {
657 // Apply a half-grid shift and impose the periodic boundary condition.
658 double loc_grid = this->params.ngrid[iaxis]
659 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis] + 0.5;
660
661 if (loc_grid > this->params.ngrid[iaxis]) {
662 loc_grid -= this->params.ngrid[iaxis];
663 }
664
665 int idx_grid = int(loc_grid);
666
667 ijk[0][iaxis] = idx_grid;
668 ijk[1][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
669 ? 0 : idx_grid + 1;
670
671 double s = loc_grid - idx_grid;
672 win[0][iaxis] = 1. - s;
673 win[1][iaxis] = s;
674 }
675
676 for (int iloc = 0; iloc < order; iloc++) {
677 for (int jloc = 0; jloc < order; jloc++) {
678 for (int kloc = 0; kloc < order; kloc++) {
679 gid = this->ret_grid_index(
680 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
681 );
682 if (0 <= gid && gid < this->params.nmesh) {
683OMP_ATOMIC
684 this->field_s[gid][0] += inv_vol_cell
685 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
686OMP_ATOMIC
687 this->field_s[gid][1] += inv_vol_cell
688 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
689 }
690 }
691 }
692 }
693 }
694 }
695}
696
697void MeshField::assign_weighted_field_to_mesh_tsc(
698 ParticleCatalogue& particles, fftw_complex* weight
699) {
700 // Set interpolation order, i.e. number of grids, per dimension,
701 // to which a single particle is assigned.
702 const int order = 3;
703
704 // Here the field is given by Σᵢ wᵢ δᴰ(x - xᵢ),
705 // where δᴰ ↔ δᴷ / dV, dV =: `vol_cell`.
706 const double inv_vol_cell = 1 / this->vol_cell;
707
708 // Reset field values to zero.
709 this->reset_density_field();
710
711 // Perform assignment.
712#ifdef TRV_USE_OMP
713#pragma omp parallel for
714#endif // TRV_USE_OMP
715 for (int pid = 0; pid < particles.ntotal; pid++) {
716 int ijk[order][3]; // grid index coordinates of covered grid cells
717 double win[order][3]; // sampling window
718 long long gid = 0; // flattened grid cell index
719
720 for (int iaxis = 0; iaxis < 3; iaxis++) {
721 // Carefully set covered sampling window grid indices.
722 double loc_grid = this->params.ngrid[iaxis]
723 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis];
724
725 int idx_grid = int(loc_grid);
726
727 if (loc_grid - idx_grid < 0.5) {
728 ijk[0][iaxis] = (idx_grid == 0)
729 ? this->params.ngrid[iaxis] - 1 : idx_grid - 1;
730 ijk[1][iaxis] = idx_grid;
731 ijk[2][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
732 ? 0 : idx_grid + 1;
733 } else {
734 ijk[0][iaxis] = idx_grid;
735 ijk[1][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
736 ? 0 : idx_grid + 1;
737 ijk[2][iaxis] = (ijk[1][iaxis] == this->params.ngrid[iaxis] - 1)
738 ? 0 : ijk[1][iaxis] + 1;
739 }
740
741 // Set sampling window value (up to the 2nd element as `order == 3`).
742 double s = loc_grid - idx_grid;
743
744 if (s < 0.5) {
745 win[0][iaxis] = 1./2 * (1./2 - s) * (1./2 - s);
746 win[1][iaxis] = 3./4 - s * s;
747 win[2][iaxis] = 1./2 * (1./2 + s) * (1./2 + s);
748 } else {
749 s = 1 - s;
750 win[0][iaxis] = 1./2 * (1./2 + s) * (1./2 + s);
751 win[1][iaxis] = 3./4 - s * s;
752 win[2][iaxis] = 1./2 * (1./2 - s) * (1./2 - s);
753 }
754 }
755
756 for (int iloc = 0; iloc < order; iloc++) {
757 for (int jloc = 0; jloc < order; jloc++) {
758 for (int kloc = 0; kloc < order; kloc++) {
759 gid = this->ret_grid_index(
760 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
761 );
762 if (0 <= gid && gid < this->params.nmesh) {
763OMP_ATOMIC
764 this->field[gid][0] += inv_vol_cell
765 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
766OMP_ATOMIC
767 this->field[gid][1] += inv_vol_cell
768 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
769 }
770 }
771 }
772 }
773 }
774
775 // Perform interlacing if needed.
776 if (this->params.interlace == "true") {
777#ifdef TRV_USE_OMP
778#pragma omp parallel for
779#endif // TRV_USE_OMP
780 for (int pid = 0; pid < particles.ntotal; pid++) {
781 int ijk[order][3];
782 double win[order][3];
783 long long gid = 0;
784
785 for (int iaxis = 0; iaxis < 3; iaxis++) {
786 // Apply a half-grid shift and impose the periodic boundary condition.
787 double loc_grid = this->params.ngrid[iaxis]
788 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis] + 0.5;
789
790 if (loc_grid > this->params.ngrid[iaxis]) {
791 loc_grid -= this->params.ngrid[iaxis];
792 }
793
794 int idx_grid = int(loc_grid);
795
796 if (loc_grid - idx_grid < 0.5) {
797 ijk[0][iaxis] = (idx_grid == 0)
798 ? this->params.ngrid[iaxis] - 1 : idx_grid - 1;
799 ijk[1][iaxis] = idx_grid;
800 ijk[2][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
801 ? 0 : idx_grid + 1;
802 } else {
803 ijk[0][iaxis] = idx_grid;
804 ijk[1][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
805 ? 0 : ijk[0][iaxis] + 1;
806 ijk[2][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
807 ? 0 : ijk[1][iaxis] + 1;
808 }
809
810 double s = loc_grid - idx_grid;
811
812 if (s < 0.5) {
813 win[0][iaxis] = 1./2 * (1./2 - s) * (1./2 - s);
814 win[1][iaxis] = 3./4 - s * s;
815 win[2][iaxis] = 1./2 * (1./2 + s) * (1./2 + s);
816 } else {
817 s = 1 - s;
818 win[0][iaxis] = 1./2 * (1./2 + s) * (1./2 + s);
819 win[1][iaxis] = 3./4 - s * s;
820 win[2][iaxis] = 1./2 * (1./2 - s) * (1./2 - s);
821 }
822 }
823
824 for (int iloc = 0; iloc < order; iloc++) {
825 for (int jloc = 0; jloc < order; jloc++) {
826 for (int kloc = 0; kloc < order; kloc++) {
827 gid = this->ret_grid_index(
828 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
829 );
830 if (0 <= gid && gid < this->params.nmesh) {
831OMP_ATOMIC
832 this->field_s[gid][0] += inv_vol_cell
833 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
834OMP_ATOMIC
835 this->field_s[gid][1] += inv_vol_cell
836 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
837 }
838 }
839 }
840 }
841 }
842 }
843}
844
845void MeshField::assign_weighted_field_to_mesh_pcs(
846 ParticleCatalogue& particles, fftw_complex* weight
847) {
848 // Set interpolation order, i.e. number of grids, per dimension,
849 // to which a single particle is assigned.
850 const int order = 4;
851
852 // Here the field is given by Σᵢ wᵢ δᴰ(x - xᵢ),
853 // where δᴰ ↔ δᴷ / dV, dV =: `vol_cell`.
854 const double inv_vol_cell = 1 / this->vol_cell;
855
856 // Reset field values to zero.
857 this->reset_density_field();
858
859 // Perform assignment.
860#ifdef TRV_USE_OMP
861#pragma omp parallel for
862#endif // TRV_USE_OMP
863 for (int pid = 0; pid < particles.ntotal; pid++) {
864 int ijk[order][3]; // grid index coordinates of covered grid cells
865 double win[order][3]; // sampling window
866 long long gid = 0; // flattened grid cell index
867
868 for (int iaxis = 0; iaxis < 3; iaxis++) {
869 // Carefully set covered sampling window grid indices.
870 double loc_grid = this->params.ngrid[iaxis]
871 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis];
872
873 int idx_grid = int(loc_grid);
874
875 ijk[0][iaxis] = (idx_grid == 0)
876 ? this->params.ngrid[iaxis] - 1 : idx_grid - 1;
877 ijk[1][iaxis] = idx_grid;
878 ijk[2][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
879 ? 0 : idx_grid + 1;
880 ijk[3][iaxis] = (ijk[2][iaxis] == this->params.ngrid[iaxis] - 1)
881 ? 0 : ijk[2][iaxis] + 1;
882
883 // Set sampling window value (up to the 2nd element as `order == 3`).
884 double s = loc_grid - idx_grid;
885
886 win[0][iaxis] = 1./6 * (1. - s) * (1. - s) * (1. - s);
887 win[1][iaxis] = 1./6 * (4. - 6. * s * s + 3. * s * s * s);
888 win[2][iaxis] = 1./6 * (
889 4. - 6. * (1. - s) * (1. - s) + 3. * (1. - s) * (1. - s) * (1. - s)
890 );
891 win[3][iaxis] = 1./6 * s * s * s;
892 }
893
894 for (int iloc = 0; iloc < order; iloc++) {
895 for (int jloc = 0; jloc < order; jloc++) {
896 for (int kloc = 0; kloc < order; kloc++) {
897 gid = this->ret_grid_index(
898 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
899 );
900 if (0 <= gid && gid < this->params.nmesh) {
901OMP_ATOMIC
902 this->field[gid][0] += inv_vol_cell
903 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
904OMP_ATOMIC
905 this->field[gid][1] += inv_vol_cell
906 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
907 }
908 }
909 }
910 }
911 }
912
913 // Perform interlacing if needed.
914 if (this->params.interlace == "true") {
915#ifdef TRV_USE_OMP
916#pragma omp parallel for
917#endif // TRV_USE_OMP
918 for (int pid = 0; pid < particles.ntotal; pid++) {
919 int ijk[order][3];
920 double win[order][3];
921 long long gid = 0;
922
923 for (int iaxis = 0; iaxis < 3; iaxis++) {
924 // Apply a half-grid shift and impose the periodic boundary condition.
925 double loc_grid = this->params.ngrid[iaxis]
926 * particles[pid].pos[iaxis] / this->params.boxsize[iaxis] + 0.5;
927
928 if (loc_grid > this->params.ngrid[iaxis]) {
929 loc_grid -= this->params.ngrid[iaxis];
930 }
931
932 int idx_grid = int(loc_grid);
933
934 ijk[0][iaxis] = (idx_grid == 0)
935 ? this->params.ngrid[iaxis] - 1 : idx_grid - 1;
936 ijk[1][iaxis] = idx_grid;
937 ijk[2][iaxis] = (idx_grid == this->params.ngrid[iaxis] - 1)
938 ? 0 : idx_grid + 1;
939 ijk[3][iaxis] = (ijk[2][iaxis] == this->params.ngrid[iaxis] - 1)
940 ? 0 : ijk[2][iaxis] + 1;
941 double s = loc_grid - idx_grid;
942
943 win[0][iaxis] = 1./6 * (1. - s) * (1. - s) * (1. - s);
944 win[1][iaxis] = 1./6 * (4. - 6. * s * s + 3. * s * s * s);
945 win[2][iaxis] = 1./6 * (
946 4. - 6. * (1. - s) * (1. - s) + 3. * (1. - s) * (1. - s) * (1. - s)
947 );
948 win[3][iaxis] = 1./6 * s * s * s;
949 }
950
951 for (int iloc = 0; iloc < order; iloc++) {
952 for (int jloc = 0; jloc < order; jloc++) {
953 for (int kloc = 0; kloc < order; kloc++) {
954 gid = this->ret_grid_index(
955 ijk[iloc][0], ijk[jloc][1], ijk[kloc][2]
956 );
957 if (0 <= gid && gid < this->params.nmesh) {
958OMP_ATOMIC
959 this->field_s[gid][0] += inv_vol_cell
960 * weight[pid][0] * win[iloc][0] * win[jloc][1] * win[kloc][2];
961OMP_ATOMIC
962 this->field_s[gid][1] += inv_vol_cell
963 * weight[pid][1] * win[iloc][0] * win[jloc][1] * win[kloc][2];
964 }
965 }
966 }
967 }
968 }
969 }
970}
971
972double MeshField::calc_assignment_window_in_fourier(
973 int i, int j, int k, int order
974) {
975 if (order < 0) {
976 if (trvs::currTask == 0) {
978 "Invalid window assignment order: %d. Must be non-negative.",
979 order
980 );
981 }
983 "Invalid window assignment order: %d. Must be non-negative.\n",
984 order
985 );
986 }
987
988 // Return the pre-computed window value.
989 if (order == this->window_assign_order) {
990 long long idx_grid = this->ret_grid_index(i, j, k);
991 return this->window[idx_grid];
992 }
993
994 this->shift_grid_indices_fourier(i, j, k);
995
996 double u_x = M_PI * i / double(this->params.ngrid[0]);
997 double u_y = M_PI * j / double(this->params.ngrid[1]);
998 double u_z = M_PI * k / double(this->params.ngrid[2]);
999
1000 // Note sin(u) / u -> 1 as u -> 0.
1001 double wk_x = (i != 0) ? std::sin(u_x) / u_x : 1.;
1002 double wk_y = (j != 0) ? std::sin(u_y) / u_y : 1.;
1003 double wk_z = (k != 0) ? std::sin(u_z) / u_z : 1.;
1004
1005 double wk = wk_x * wk_y * wk_z;
1006
1007 return std::pow(wk, order);
1008}
1009
1010void MeshField::compute_assignment_window_in_fourier(int order) {
1011 if (order < 0) {
1012 if (trvs::currTask == 0) {
1014 "Invalid window assignment order: %d. Must be non-negative.",
1015 order
1016 );
1017 }
1019 "Invalid window assignment order: %d. Must be non-negative.\n",
1020 order
1021 );
1022 }
1023
1024 if (this->window_assign_order == order) {return;} // if computed already
1025
1026 if (trvs::currTask == 0) {
1028 "Computing interpolation window in Fourier space "
1029 "for assignment order %d.",
1030 order
1031 );
1032 }
1033
1034 if (this->window_assign_order == -1) {
1035 this->window.resize(this->params.nmesh, 0.); // if not yet initialised
1036
1037 trvs::count_rgrid += 1;
1038 trvs::count_grid += .5;
1042 }
1043
1044#ifdef TRV_USE_OMP
1045#pragma omp parallel for collapse(3)
1046#endif // TRV_USE_OMP
1047 for (int i = 0; i < this->params.ngrid[0]; i++) {
1048 for (int j = 0; j < this->params.ngrid[1]; j++) {
1049 for (int k = 0; k < this->params.ngrid[2]; k++) {
1050 long long idx_grid = this->ret_grid_index(i, j, k);
1051 this->window[idx_grid] = this->calc_assignment_window_in_fourier(
1052 i, j, k, order
1053 );
1054 }
1055 }
1056 }
1057
1058 this->window_assign_order = order; // set assignment order/flag
1059}
1060
1061
1062// -----------------------------------------------------------------------
1063// Field computations
1064// -----------------------------------------------------------------------
1065
1067 fftw_complex* unit_weight = nullptr;
1068
1069 unit_weight = fftw_alloc_complex(particles.ntotal);
1070
1073
1074#ifdef TRV_USE_OMP
1075#pragma omp parallel for simd
1076#endif // TRV_USE_OMP
1077 for (int pid = 0; pid < particles.ntotal; pid++) {
1078 unit_weight[pid][0] = 1.;
1079 unit_weight[pid][1] = 0.;
1080 }
1081
1082 this->assign_weighted_field_to_mesh(particles, unit_weight);
1083
1084 fftw_free(unit_weight); unit_weight = nullptr;
1085
1087}
1088
1090 ParticleCatalogue& particles
1091) {
1092 this->compute_unweighted_field(particles);
1093
1094 // Subtract the global mean density to compute fluctuations, i.e. δn.
1095 double nbar = double(particles.ntotal) / this->vol;
1096
1097#ifdef TRV_USE_OMP
1098#pragma omp parallel for simd
1099#endif // TRV_USE_OMP
1100 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1101 this->field[gid][0] -= nbar;
1102 // this->field[gid][1] -= 0.; (unused)
1103 }
1104}
1105
1107 ParticleCatalogue& particles_data, ParticleCatalogue& particles_rand,
1108 LineOfSight* los_data, LineOfSight* los_rand,
1109 double alpha, int ell, int m
1110) {
1111 fftw_complex* weight_kern = nullptr;
1112
1113 // Compute the weighted data-source field.
1114 weight_kern = fftw_alloc_complex(particles_data.ntotal);
1115
1118
1119#ifdef TRV_USE_OMP
1120#pragma omp parallel for
1121#endif // TRV_USE_OMP
1122 for (int pid = 0; pid < particles_data.ntotal; pid++) {
1123 double los_[3] = {
1124 los_data[pid].pos[0], los_data[pid].pos[1], los_data[pid].pos[2]
1125 };
1126
1127 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
1128 calc_reduced_spherical_harmonic(ell, m, los_);
1129
1130 weight_kern[pid][0] = ylm.real() * particles_data[pid].w;
1131 weight_kern[pid][1] = ylm.imag() * particles_data[pid].w;
1132 }
1133
1134 this->assign_weighted_field_to_mesh(particles_data, weight_kern);
1135
1136 fftw_free(weight_kern); weight_kern = nullptr;
1137
1139
1140 // Compute the weighted random-source field.
1141 weight_kern = fftw_alloc_complex(particles_rand.ntotal);
1142
1145
1146#ifdef TRV_USE_OMP
1147#pragma omp parallel for
1148#endif // TRV_USE_OMP
1149 for (int pid = 0; pid < particles_rand.ntotal; pid++) {
1150 double los_[3] = {
1151 los_rand[pid].pos[0], los_rand[pid].pos[1], los_rand[pid].pos[2]
1152 };
1153
1154 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
1155 calc_reduced_spherical_harmonic(ell, m, los_);
1156
1157 weight_kern[pid][0] = ylm.real() * particles_rand[pid].w;
1158 weight_kern[pid][1] = ylm.imag() * particles_rand[pid].w;
1159 }
1160
1161 MeshField field_rand(this->params, false, "`field_rand`");
1162 field_rand.assign_weighted_field_to_mesh(particles_rand, weight_kern);
1163
1164 fftw_free(weight_kern); weight_kern = nullptr;
1165
1167
1168 // Subtract to compute fluctuations, i.e. δn_LM.
1169#ifdef TRV_USE_OMP
1170#pragma omp parallel for simd
1171#endif // TRV_USE_OMP
1172 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1173 this->field[gid][0] -= alpha * field_rand[gid][0];
1174 this->field[gid][1] -= alpha * field_rand[gid][1];
1175 }
1176
1177 if (this->params.interlace == "true") {
1178#ifdef TRV_USE_OMP
1179#pragma omp parallel for simd
1180#endif // TRV_USE_OMP
1181 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1182 this->field_s[gid][0] -= alpha * field_rand.field_s[gid][0];
1183 this->field_s[gid][1] -= alpha * field_rand.field_s[gid][1];
1184 }
1185 }
1186}
1187
1189 ParticleCatalogue& particles, LineOfSight* los,
1190 double alpha, int ell, int m
1191) {
1192 fftw_complex* weight_kern = nullptr;
1193
1194 // Compute the weighted field.
1195 weight_kern = fftw_alloc_complex(particles.ntotal);
1196
1199
1200#ifdef TRV_USE_OMP
1201#pragma omp parallel for
1202#endif // TRV_USE_OMP
1203 for (int pid = 0; pid < particles.ntotal; pid++) {
1204 double los_[3] = {los[pid].pos[0], los[pid].pos[1], los[pid].pos[2]};
1205
1206 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
1207 calc_reduced_spherical_harmonic(ell, m, los_);
1208
1209 weight_kern[pid][0] = ylm.real() * particles[pid].w;
1210 weight_kern[pid][1] = ylm.imag() * particles[pid].w;
1211 }
1212
1213 this->assign_weighted_field_to_mesh(particles, weight_kern);
1214
1215 fftw_free(weight_kern); weight_kern = nullptr;
1216
1218
1219 // Apply the normalising alpha contrast.
1220#ifdef TRV_USE_OMP
1221#pragma omp parallel for simd
1222#endif // TRV_USE_OMP
1223 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1224 this->field[gid][0] *= alpha;
1225 this->field[gid][1] *= alpha;
1226 }
1227}
1228
1230 ParticleCatalogue& particles_data, ParticleCatalogue& particles_rand,
1231 LineOfSight* los_data, LineOfSight* los_rand,
1232 double alpha,
1233 int ell, int m
1234) {
1235 fftw_complex* weight_kern = nullptr;
1236
1237 // Compute the quadratic weighted data-source field.
1238 weight_kern = fftw_alloc_complex(particles_data.ntotal);
1239
1242
1243#ifdef TRV_USE_OMP
1244#pragma omp parallel for
1245#endif // TRV_USE_OMP
1246 for (int pid = 0; pid < particles_data.ntotal; pid++) {
1247 double los_[3] = {
1248 los_data[pid].pos[0], los_data[pid].pos[1], los_data[pid].pos[2]
1249 };
1250
1251 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
1252 calc_reduced_spherical_harmonic(ell, m, los_);
1253
1254 ylm = std::conj(ylm); // additional conjugation
1255
1256 weight_kern[pid][0] = ylm.real() * std::pow(particles_data[pid].w, 2);
1257 weight_kern[pid][1] = ylm.imag() * std::pow(particles_data[pid].w, 2);
1258 }
1259
1260 this->assign_weighted_field_to_mesh(particles_data, weight_kern);
1261
1262 fftw_free(weight_kern); weight_kern = nullptr;
1263
1265
1266 // Compute the quadratic weighted random-source field.
1267 weight_kern = fftw_alloc_complex(particles_rand.ntotal);
1268
1271
1272#ifdef TRV_USE_OMP
1273#pragma omp parallel for
1274#endif // TRV_USE_OMP
1275 for (int pid = 0; pid < particles_rand.ntotal; pid++) {
1276 double los_[3] = {
1277 los_rand[pid].pos[0], los_rand[pid].pos[1], los_rand[pid].pos[2]
1278 };
1279
1280 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
1281 calc_reduced_spherical_harmonic(ell, m, los_);
1282
1283 ylm = std::conj(ylm); // additional conjugation
1284
1285 weight_kern[pid][0] = ylm.real() * std::pow(particles_rand[pid].w, 2);
1286 weight_kern[pid][1] = ylm.imag() * std::pow(particles_rand[pid].w, 2);
1287 }
1288
1289 MeshField field_rand(this->params, false, "`field_rand`");
1290 field_rand.assign_weighted_field_to_mesh(particles_rand, weight_kern);
1291
1292 fftw_free(weight_kern); weight_kern = nullptr;
1293
1295
1296 // Add to compute quadratic fluctuations, i.e. N_LM.
1297#ifdef TRV_USE_OMP
1298#pragma omp parallel for simd
1299#endif // TRV_USE_OMP
1300 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1301 this->field[gid][0] += std::pow(alpha, 2) * field_rand[gid][0];
1302 this->field[gid][1] += std::pow(alpha, 2) * field_rand[gid][1];
1303 }
1304
1305 if (this->params.interlace == "true") {
1306#ifdef TRV_USE_OMP
1307#pragma omp parallel for simd
1308#endif // TRV_USE_OMP
1309 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1310 this->field_s[gid][0] += std::pow(alpha, 2) * field_rand.field_s[gid][0];
1311 this->field_s[gid][1] += std::pow(alpha, 2) * field_rand.field_s[gid][1];
1312 }
1313 }
1314}
1315
1317 ParticleCatalogue& particles, LineOfSight* los,
1318 double alpha, int ell, int m
1319) {
1320 fftw_complex* weight_kern = nullptr;
1321
1322 // Compute the quadratic weighted field.
1323 weight_kern = fftw_alloc_complex(particles.ntotal);
1324
1327
1328#ifdef TRV_USE_OMP
1329#pragma omp parallel for
1330#endif // TRV_USE_OMP
1331 for (int pid = 0; pid < particles.ntotal; pid++) {
1332 double los_[3] = {los[pid].pos[0], los[pid].pos[1], los[pid].pos[2]};
1333
1334 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
1335 calc_reduced_spherical_harmonic(ell, m, los_);
1336
1337 ylm = std::conj(ylm); // conjugation is essential
1338
1339 weight_kern[pid][0] = ylm.real() * std::pow(particles[pid].w, 2);
1340 weight_kern[pid][1] = ylm.imag() * std::pow(particles[pid].w, 2);
1341 }
1342
1343 this->assign_weighted_field_to_mesh(particles, weight_kern);
1344
1345 fftw_free(weight_kern); weight_kern = nullptr;
1346
1348
1349 // Apply mean-density matching normalisation (i.e. alpha contrast)
1350 // to compute N_LM.
1351#ifdef TRV_USE_OMP
1352#pragma omp parallel for simd
1353#endif // TRV_USE_OMP
1354 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1355 this->field[gid][0] *= std::pow(alpha, 2);
1356 this->field[gid][1] *= std::pow(alpha, 2);
1357 }
1358}
1359
1360
1361// -----------------------------------------------------------------------
1362// Field transforms
1363// -----------------------------------------------------------------------
1364
1366 if (trvs::currTask == 0) {
1368 "Performing Fourier transform of '%s'.", this->name.c_str()
1369 );
1370 }
1371
1372 // Apply FFT volume normalisation, where ∫d³x ↔ dV Σᵢ, dV =: `vol_cell`.
1373#ifdef TRV_USE_OMP
1374#pragma omp parallel for simd
1375#endif // TRV_USE_OMP
1376 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1377 this->field[gid][0] *= this->vol_cell;
1378 this->field[gid][1] *= this->vol_cell;
1379 }
1380
1381 // Perform FFT.
1382 if (this->plan_ext) {
1383 fftw_execute_dft(this->transform, this->field, this->field);
1384 } else {
1385 fftw_execute(this->transform);
1386 }
1387 trvs::count_fft += 1;
1388
1389 // Interlace with the shadow field.
1390 if (this->params.interlace == "true") {
1391#ifdef TRV_USE_OMP
1392#pragma omp parallel for simd
1393#endif // TRV_USE_OMP
1394 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1395 this->field_s[gid][0] *= this->vol_cell;
1396 this->field_s[gid][1] *= this->vol_cell;
1397 }
1398
1399 if (this->plan_ext) {
1400 fftw_execute_dft(this->transform_s, this->field_s, this->field_s);
1401 } else {
1402 fftw_execute(this->transform_s);
1403 }
1404 trvs::count_fft += 1;
1405
1406#ifdef TRV_USE_OMP
1407#pragma omp parallel for collapse(3)
1408#endif // TRV_USE_OMP
1409 for (int i = 0; i < this->params.ngrid[0]; i++) {
1410 for (int j = 0; j < this->params.ngrid[1]; j++) {
1411 for (int k = 0; k < this->params.ngrid[2]; k++) {
1412 long long idx_grid = this->ret_grid_index(i, j, k);
1413
1414 // Calculate the index vector representing the grid cell.
1415 double m[3];
1416 m[0] = (i < this->params.ngrid[0]/2)
1417 ? double(i) / this->params.ngrid[0]
1418 : double(i) / this->params.ngrid[0] - 1;
1419 m[1] = (j < this->params.ngrid[1]/2)
1420 ? double(j) / this->params.ngrid[1]
1421 : double(j) / this->params.ngrid[1] - 1;
1422 m[2] = (k < this->params.ngrid[2]/2)
1423 ? double(k) / this->params.ngrid[2]
1424 : double(k) / this->params.ngrid[2] - 1;
1425
1426 // Multiply by the phase factor from the half-grid shift and
1427 // add the shadow mesh field contribution. Note the positive
1428 // sign of `arg`.
1429 double arg = M_PI * (m[0] + m[1] + m[2]);
1430
1431 this->field[idx_grid][0] +=
1432 std::cos(arg) * this->field_s[idx_grid][0]
1433 - std::sin(arg) * this->field_s[idx_grid][1]
1434 ;
1435 this->field[idx_grid][1] +=
1436 std::sin(arg) * this->field_s[idx_grid][0]
1437 + std::cos(arg) * this->field_s[idx_grid][1]
1438 ;
1439
1440 this->field[idx_grid][0] /= 2.;
1441 this->field[idx_grid][1] /= 2.;
1442 }
1443 }
1444 }
1445 }
1446}
1447
1449 if (trvs::currTask == 0) {
1451 "Performing inverse Fourier transform of '%s'.", this->name.c_str()
1452 );
1453 }
1454
1455 // Apply inverse FFT volume normalisation, where ∫d³k/(2π)³ ↔ (1/V) Σᵢ,
1456 // V =: `vol`.
1457#ifdef TRV_USE_OMP
1458#pragma omp parallel for simd
1459#endif // TRV_USE_OMP
1460 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1461 this->field[gid][0] /= this->vol;
1462 this->field[gid][1] /= this->vol;
1463 }
1464
1465 // Perform inverse FFT.
1466 if (this->plan_ext) {
1467 fftw_execute_dft(this->inv_transform, this->field, this->field);
1468 } else {
1469 fftw_execute(this->inv_transform);
1470 }
1471 trvs::count_ifft += 1;
1472}
1473
1474
1475// -----------------------------------------------------------------------
1476// Field operations
1477// -----------------------------------------------------------------------
1478
1480 if (trvs::currTask == 0) {
1482 "Applying wide-angle power-law kernel to '%s'.", this->name.c_str()
1483 );
1484 }
1485
1486 // CAVEAT: Discretionary choice such that eps_r / r = O(1.e-9).
1487 const double eps_r = 1.e-6;
1488
1489#ifdef TRV_USE_OMP
1490#pragma omp parallel for collapse(3)
1491#endif // TRV_USE_OMP
1492 for (int i = 0; i < this->params.ngrid[0]; i++) {
1493 for (int j = 0; j < this->params.ngrid[1]; j++) {
1494 for (int k = 0; k < this->params.ngrid[2]; k++) {
1495 long long idx_grid = this->ret_grid_index(i, j, k);
1496
1497 double rv[3];
1498 this->get_grid_pos_vector(i, j, k, rv);
1499
1500 double r_ = trvm::get_vec3d_magnitude(rv);
1501
1502 if (r_ < eps_r) {
1503 // this->field[idx_grid][0] *= 0.; (unused)
1504 // this->field[idx_grid][1] *= 0.; (unused)
1505 } else {
1506 this->field[idx_grid][0] *=
1507 std::pow(r_, - this->params.i_wa - this->params.j_wa);
1508 this->field[idx_grid][1] *=
1509 std::pow(r_, - this->params.i_wa - this->params.j_wa);
1510 }
1511 }
1512 }
1513 }
1514}
1515
1517 if (trvs::currTask == 0) {
1519 "Applying assignment compensation to '%s'.", this->name.c_str()
1520 );
1521 }
1522
1523 this->compute_assignment_window_in_fourier(this->params.assignment_order);
1524
1525#ifdef TRV_USE_OMP
1526#pragma omp parallel for collapse(3)
1527#endif // TRV_USE_OMP
1528 for (int i = 0; i < this->params.ngrid[0]; i++) {
1529 for (int j = 0; j < this->params.ngrid[1]; j++) {
1530 for (int k = 0; k < this->params.ngrid[2]; k++) {
1531 long long idx_grid = this->ret_grid_index(i, j, k);
1532 this->field[idx_grid][0] /= this->window[idx_grid];
1533 this->field[idx_grid][1] /= this->window[idx_grid];
1534 }
1535 }
1536 }
1537}
1538
1539
1540// -----------------------------------------------------------------------
1541// One-point statistics
1542// -----------------------------------------------------------------------
1543
1545 MeshField& field_fourier, std::vector< std::complex<double> >& ylm,
1546 double k_lower, double k_upper,
1547 double& k_eff, int& nmodes
1548) {
1549 if (trvs::currTask == 0) {
1551 "Performing inverse Fourier transform to spherical harmonic weighted "
1552 "'%s' in wavenumber bands [%f, %f).",
1553 this->name.c_str(), k_lower, k_upper
1554 );
1555 }
1556
1557 // The nested for-loops below should cover all grid point computations
1558 // so that this is redundant. [tag:redundancy]
1559 // // Reset field values to zero.
1560 // this->reset_density_field();
1561
1562 // Reset effective wavenumber and wavevector modes.
1563 k_eff = 0.;
1564 nmodes = 0;
1565
1566 // Perform wavevector mode binning in the band.
1567 this->compute_assignment_window_in_fourier(this->params.assignment_order);
1568
1569#ifdef TRV_USE_OMP
1570#pragma omp parallel for collapse(3) reduction(+:k_eff, nmodes)
1571#endif // TRV_USE_OMP
1572 for (int i = 0; i < this->params.ngrid[0]; i++) {
1573 for (int j = 0; j < this->params.ngrid[1]; j++) {
1574 for (int k = 0; k < this->params.ngrid[2]; k++) {
1575 long long idx_grid = this->ret_grid_index(i, j, k);
1576
1577 double kv[3];
1578 this->get_grid_wavevector(i, j, k, kv);
1579
1580 double k_ = trvm::get_vec3d_magnitude(kv);
1581
1582 // Determine the grid cell contribution to the band.
1583 if (k_lower <= k_ && k_ < k_upper) {
1584 std::complex<double> fk(
1585 field_fourier[idx_grid][0], field_fourier[idx_grid][1]
1586 );
1587
1588 // Apply assignment compensation.
1589 fk /= this->window[idx_grid];
1590
1591 // Weight the field.
1592 this->field[idx_grid][0] = (ylm[idx_grid] * fk).real();
1593 this->field[idx_grid][1] = (ylm[idx_grid] * fk).imag();
1594
1595 k_eff += k_;
1596 nmodes++;
1597 } else {
1598 // This is necessary when the field is not reset to zero.
1599 // See comment [tag:redundancy] above.
1600 this->field[idx_grid][0] = 0.;
1601 this->field[idx_grid][1] = 0.;
1602 }
1603 }
1604 }
1605 }
1606
1607 // Perform inverse FFT.
1608 if (this->plan_ext) {
1609 fftw_execute_dft(this->inv_transform, this->field, this->field);
1610 } else {
1611 fftw_execute(this->inv_transform);
1612 }
1613 trvs::count_ifft += 1;
1614
1615 // Average over wavevector modes in the band.
1616#ifdef TRV_USE_OMP
1617#pragma omp parallel for simd
1618#endif // TRV_USE_OMP
1619 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1620 this->field[gid][0] /= double(nmodes);
1621 this->field[gid][1] /= double(nmodes);
1622 }
1623
1624 k_eff /= double(nmodes);
1625}
1626
1628 MeshField& field_fourier,
1629 std::vector< std::complex<double> >& ylm,
1631 double r
1632) {
1633 if (trvs::currTask == 0) {
1635 "Performing inverse Fourier transform to spherical Bessel weighted "
1636 "'%s' at separation r = %f.",
1637 this->name.c_str(), r
1638 );
1639 }
1640
1641 // The nested for-loops below cover all grid point computations
1642 // so this is redundant.
1643 // // Reset field values to zero.
1644 // this->reset_density_field();
1645
1646 // Compute the field weighted by the spherical Bessel function and
1647 // reduced spherical harmonics.
1648 this->compute_assignment_window_in_fourier(this->params.assignment_order);
1649
1650#ifdef TRV_USE_OMP
1651#pragma omp parallel
1652#endif // TRV_USE_OMP
1653{
1654 // Create thread-private copies of the spherical Bessel function calculator.
1655 trvm::SphericalBesselCalculator sjl_thread(sjl);
1656
1657#ifdef TRV_USE_OMP
1658#pragma omp for collapse(3)
1659#endif // TRV_USE_OMP
1660 for (int i = 0; i < this->params.ngrid[0]; i++) {
1661 for (int j = 0; j < this->params.ngrid[1]; j++) {
1662 for (int k = 0; k < this->params.ngrid[2]; k++) {
1663 long long idx_grid = this->ret_grid_index(i, j, k);
1664
1665 double kv[3];
1666 this->get_grid_wavevector(i, j, k, kv);
1667
1668 double k_ = trvm::get_vec3d_magnitude(kv);
1669
1670 // Apply assignment compensation.
1671 std::complex<double> fk(
1672 field_fourier[idx_grid][0], field_fourier[idx_grid][1]
1673 );
1674
1675 fk /= this->window[idx_grid];
1676
1677 // Weight the field including the volume normalisation,
1678 // where ∫d³k/(2π)³ ↔ (1/V) Σᵢ, V =: `vol`.
1679 this->field[idx_grid][0] =
1680 sjl_thread.eval(k_ * r) * (ylm[idx_grid] * fk).real() / this->vol;
1681 this->field[idx_grid][1] =
1682 sjl_thread.eval(k_ * r) * (ylm[idx_grid] * fk).imag() / this->vol;
1683 }
1684 }
1685 }
1686}
1687
1688 // Perform inverse FFT.
1689 if (this->plan_ext) {
1690 fftw_execute_dft(this->inv_transform, this->field, this->field);
1691 } else {
1692 fftw_execute(this->inv_transform);
1693 }
1694 trvs::count_ifft += 1;
1695}
1696
1697
1698// -----------------------------------------------------------------------
1699// Misc
1700// -----------------------------------------------------------------------
1701
1703 ParticleCatalogue& particles, int order
1704) {
1705 // Initialise the weight field.
1706 fftw_complex* weight = nullptr;
1707
1708 weight = fftw_alloc_complex(particles.ntotal);
1709
1712
1713#ifdef TRV_USE_OMP
1714#if defined(__GNUC__) && !defined(__clang__)
1715#pragma omp parallel for simd
1716#else // !__GNUC__ || __clang__
1717#pragma omp parallel for
1718#endif // __GNUC__ && !__clang__
1719#endif // TRV_USE_OMP
1720 for (int pid = 0; pid < particles.ntotal; pid++) {
1721 weight[pid][0] = particles[pid].w;
1722 weight[pid][1] = 0.;
1723 }
1724
1725 // Compute the weighted field.
1726 this->assign_weighted_field_to_mesh(particles, weight);
1727
1728 fftw_free(weight); weight = nullptr;
1729
1731
1732 // Compute normalisation volume integral, where ∫d³x ↔ dV Σᵢ,
1733 // dV =: `vol_cell`.
1734 double vol_int = 0.;
1735
1736#ifdef TRV_USE_OMP
1737#if defined(__GNUC__) && !defined(__clang__)
1738#pragma omp parallel for simd reduction(+:vol_int)
1739#else // !__GNUC__ || __clang__
1740#pragma omp parallel for reduction(+:vol_int)
1741#endif // __GNUC__ && !__clang__
1742#endif // TRV_USE_OMP
1743 for (long long gid = 0; gid < this->params.nmesh; gid++) {
1744 vol_int += std::pow(this->field[gid][0], order);
1745 }
1746
1747 vol_int *= this->vol_cell;
1748
1749 double norm_factor = 1. / vol_int;
1750
1751 return norm_factor;
1752}
1753
1754
1755// ***********************************************************************
1756// Field statistics
1757// ***********************************************************************
1758
1759// -----------------------------------------------------------------------
1760// Life cycle
1761// -----------------------------------------------------------------------
1762
1764 this->params = params;
1765
1766 this->reset_stats();
1767
1768 // Calculate grid sizes in configuration space.
1769 this->dr[0] = this->params.boxsize[0] / this->params.ngrid[0];
1770 this->dr[1] = this->params.boxsize[1] / this->params.ngrid[1];
1771 this->dr[2] = this->params.boxsize[2] / this->params.ngrid[2];
1772
1773 // Calculate fundamental wavenumbers in Fourier space.
1774 this->dk[0] = 2.*M_PI / this->params.boxsize[0];
1775 this->dk[1] = 2.*M_PI / this->params.boxsize[1];
1776 this->dk[2] = 2.*M_PI / this->params.boxsize[2];
1777
1778 // Calculate mesh volume and mesh grid cell volume.
1779 this->vol = this->params.volume;
1780 this->vol_cell = this->vol / double(this->params.nmesh);
1781
1782 // Set up FFTW plans.
1783 if (plan_ini) {
1784 this->twopt_3d = fftw_alloc_complex(this->params.nmesh);
1785
1786 trvs::count_cgrid += 1;
1787 trvs::count_grid += 1;
1791
1792#if defined(TRV_USE_OMP) && defined(TRV_USE_FFTWOMP)
1793 fftw_plan_with_nthreads(omp_get_max_threads());
1794#endif // TRV_USE_OMP && TRV_USE_FFTWOMP
1795 this->inv_transform = fftw_plan_dft_3d(
1796 this->params.ngrid[0], this->params.ngrid[1], this->params.ngrid[2],
1797 this->twopt_3d, this->twopt_3d,
1798 FFTW_BACKWARD, this->params.fftw_planner_flag
1799 );
1800
1801 this->plan_ini = true;
1802 }
1803}
1804
1806 if (this->alias_ini) {
1807 trvs::count_rgrid -= 1;
1808 trvs::count_grid -= .5;
1810 }
1811
1812 if (this->plan_ini) {
1813 fftw_destroy_plan(this->inv_transform);
1814 fftw_free(this->twopt_3d); this->twopt_3d = nullptr;
1815 trvs::count_cgrid -= 1;
1816 trvs::count_grid -= 1;
1818 }
1819}
1820
1822 std::fill(this->nmodes.begin(), this->nmodes.end(), 0);
1823 std::fill(this->npairs.begin(), this->npairs.end(), 0);
1824 std::fill(this->k.begin(), this->k.end(), 0.);
1825 std::fill(this->r.begin(), this->r.end(), 0.);
1826 std::fill(this->sn.begin(), this->sn.end(), 0.);
1827 std::fill(this->pk.begin(), this->pk.end(), 0.);
1828 std::fill(this->xi.begin(), this->xi.end(), 0.);
1829}
1830
1831void FieldStats::resize_stats(int num_bins){
1832 this->nmodes.resize(num_bins);
1833 this->npairs.resize(num_bins);
1834 this->k.resize(num_bins);
1835 this->r.resize(num_bins);
1836 this->sn.resize(num_bins);
1837 this->pk.resize(num_bins);
1838 this->xi.resize(num_bins);
1839}
1840
1841
1842// -----------------------------------------------------------------------
1843// Utilities
1844// -----------------------------------------------------------------------
1845
1846bool FieldStats::if_fields_compatible(
1847 MeshField& field_a, MeshField& field_b
1848) {
1849 bool flag_compatible = true;
1850
1851 // Check if physical dimensions match.
1852 for (int iaxis = 0; iaxis < 3; iaxis++) {
1853 if (
1854 this->params.boxsize[iaxis] != field_a.params.boxsize[iaxis]
1855 || this->params.boxsize[iaxis] != field_b.params.boxsize[iaxis]
1856 || this->params.ngrid[iaxis] != field_a.params.ngrid[iaxis]
1857 || this->params.ngrid[iaxis] != field_b.params.ngrid[iaxis]
1858 ) {
1859 flag_compatible = false;
1860 }
1861 }
1862
1863 // Check if derived physical dimensions match. This is usually
1864 // redundant but parameters may have been altered.
1865 if (
1866 this->params.nmesh != field_a.params.nmesh
1867 || this->params.nmesh != field_b.params.nmesh
1868 || this->params.volume != field_b.params.volume
1869 || this->params.volume != field_b.params.volume
1870 ) {
1871 flag_compatible = false;
1872 }
1873
1874 return flag_compatible;
1875}
1876
1877long long FieldStats::ret_grid_index(int i, int j, int k) {
1878 long long idx_grid =
1879 (i * static_cast<long long>(this->params.ngrid[1]) + j)
1880 * this->params.ngrid[2] + k;
1881 return idx_grid;
1882}
1883
1884void FieldStats::shift_grid_indices_fourier(int& i, int& j, int& k) {
1885 i = (i < this->params.ngrid[0]/2) ? i : i - this->params.ngrid[0];
1886 j = (j < this->params.ngrid[1]/2) ? j : j - this->params.ngrid[1];
1887 k = (k < this->params.ngrid[2]/2) ? k : k - this->params.ngrid[2];
1888}
1889
1891 trv::Binning& binning, const std::string& save_file={}
1892) {
1893 double cellsizes[3];
1894 if (binning.space == "config") {
1895 cellsizes[0] = this->dr[0];
1896 cellsizes[1] = this->dr[1];
1897 cellsizes[2] = this->dr[2];
1898 } else
1899 if (binning.space == "fourier") {
1900 cellsizes[0] = this->dk[0];
1901 cellsizes[1] = this->dk[1];
1902 cellsizes[2] = this->dk[2];
1903 } else {
1904 if (trvs::currTask == 0) {
1906 "Invalid binning space: '%s'.", binning.space.c_str()
1907 );
1908 }
1910 "Invalid binning space: '%s'.\n", binning.space.c_str()
1911 );
1912 }
1913
1914 // Restrict the mesh grid index ranges.
1915 double b = binning.bin_max;
1916
1917 int lrange_upper[3], rrange_lower[3];
1918 if (binning.space == "config") {
1919 for (int iaxis = 0; iaxis < 3; iaxis++) {
1920 lrange_upper[iaxis] = std::min(
1921 std::ceil(b * this->params.ngrid[iaxis] / this->params.boxsize[iaxis]),
1922 std::ceil(this->params.ngrid[iaxis]/2.)
1923 );
1924 rrange_lower[iaxis] = std::max(
1925 std::floor(
1926 this->params.ngrid[iaxis]
1927 - b * this->params.ngrid[iaxis] / this->params.boxsize[iaxis]
1928 ),
1929 std::floor(this->params.ngrid[iaxis]/2.)
1930 );
1931 }
1932 } else
1933 if (binning.space == "fourier") {
1934 for (int iaxis = 0; iaxis < 3; iaxis++) {
1935 lrange_upper[iaxis] = std::min(
1936 std::ceil(b * this->params.boxsize[iaxis] / (2*M_PI)),
1937 std::ceil(this->params.ngrid[iaxis]/2.)
1938 );
1939 rrange_lower[iaxis] = std::max(
1940 std::floor(
1941 this->params.ngrid[iaxis]
1942 - b * this->params.boxsize[iaxis] / (2*M_PI)
1943 ),
1944 std::floor(this->params.ngrid[iaxis]/2.)
1945 );
1946 }
1947 }
1948
1949 auto generate_range = [](
1950 int lrange_lower, int lrange_upper, int rrange_lower, int rrange_upper
1951 ) {
1952 std::vector<int> range_vector;
1953 if (lrange_upper < rrange_lower) {
1954 for (int i = lrange_lower; i <= lrange_upper; i++) {
1955 range_vector.push_back(i);
1956 }
1957 for (int i = rrange_lower; i <= rrange_upper; i++) {
1958 range_vector.push_back(i);
1959 }
1960 } else {
1961 // This can happen when sampling beyond the Nyquist scale.
1962 for (int i = lrange_lower; i <= rrange_upper; i++) {
1963 range_vector.push_back(i);
1964 }
1965 }
1966 return range_vector;
1967 };
1968
1969 std::vector<int> i_range = generate_range(
1970 0, lrange_upper[0], rrange_lower[0], params.ngrid[0] - 1
1971 );
1972 std::vector<int> j_range = generate_range(
1973 0, lrange_upper[1], rrange_lower[1], params.ngrid[1] - 1
1974 );
1975 std::vector<int> k_range = generate_range(
1976 0, lrange_upper[2], rrange_lower[2], params.ngrid[2] - 1
1977 );
1978
1979 // Record the binned vectors.
1980 trv::BinnedVectors binned_vectors;
1981
1982 binned_vectors.num_bins = binning.num_bins;
1983
1984#ifdef TRV_USE_OMP
1985#pragma omp parallel for collapse(3)
1986#endif // TRV_USE_OMP
1987 for (int i: i_range) {
1988 for (int j: j_range) {
1989 for (int k: k_range) {
1990 double vx = (i < this->params.ngrid[0]/2) ?
1991 i * cellsizes[0] : (i - this->params.ngrid[0]) * cellsizes[0];
1992 double vy = (j < this->params.ngrid[1]/2) ?
1993 j * cellsizes[1] : (j - this->params.ngrid[1]) * cellsizes[1];
1994 double vz = (k < this->params.ngrid[2]/2) ?
1995 k * cellsizes[2] : (k - this->params.ngrid[2]) * cellsizes[2];
1996
1997 double scale = trvm::get_vec3d_magnitude({vx, vy, vz});
1998
1999OMP_CRITICAL
2000{
2001 for (int ibin = 0; ibin < binning.num_bins; ibin++) {
2002 double bin_lower = binning.bin_edges[ibin];
2003 double bin_upper = binning.bin_edges[ibin + 1];
2004 if (bin_lower <= scale && scale < bin_upper) {
2005 binned_vectors.indices.push_back(ibin);
2006 binned_vectors.lower_edges.push_back(bin_lower);
2007 binned_vectors.upper_edges.push_back(bin_upper);
2008 binned_vectors.vecx.push_back(vx);
2009 binned_vectors.vecy.push_back(vy);
2010 binned_vectors.vecz.push_back(vz);
2011 binned_vectors.count++;
2012 break;
2013 }
2014 }
2015}
2016 }
2017 }
2018 }
2019
2020 trvs::gbytesMem += trvs::size_in_gb<double>(6*binned_vectors.count);
2022
2023 // Sort the binned vectors.
2024 trv::BinnedVectors binned_vectors_sorted;
2025
2026 binned_vectors_sorted.count = binned_vectors.count;
2027 binned_vectors_sorted.num_bins = binned_vectors.num_bins;
2028
2029 binned_vectors_sorted.indices.resize(binned_vectors.count);
2030 binned_vectors_sorted.lower_edges.resize(binned_vectors.count);
2031 binned_vectors_sorted.upper_edges.resize(binned_vectors.count);
2032 binned_vectors_sorted.vecx.resize(binned_vectors.count);
2033 binned_vectors_sorted.vecy.resize(binned_vectors.count);
2034 binned_vectors_sorted.vecz.resize(binned_vectors.count);
2035
2036 trvs::gbytesMem += trvs::size_in_gb<double>(6*binned_vectors.count);
2038
2039 std::vector<int> indices_sorted =
2041
2042 trvs::gbytesMem += trvs::size_in_gb<int>(binned_vectors.count);
2044
2045#ifdef TRV_USE_OMP
2046#pragma omp parallel for
2047#endif // TRV_USE_OMP
2048 for (int i = 0; i < binned_vectors.count; i++) {
2049 int idx = indices_sorted[i];
2050 binned_vectors_sorted.indices[i] = binned_vectors.indices[idx];
2051 binned_vectors_sorted.lower_edges[i] = binned_vectors.lower_edges[idx];
2052 binned_vectors_sorted.upper_edges[i] = binned_vectors.upper_edges[idx];
2053 binned_vectors_sorted.vecx[i] = binned_vectors.vecx[idx];
2054 binned_vectors_sorted.vecy[i] = binned_vectors.vecy[idx];
2055 binned_vectors_sorted.vecz[i] = binned_vectors.vecz[idx];
2056 }
2057
2058 // Save the binned vectors.
2059 if (save_file != "") {
2060 std::FILE* save_fileptr = std::fopen(save_file.c_str(), "w");
2062 save_fileptr, this->params, binned_vectors_sorted
2063 );
2064 std::fclose(save_fileptr);
2065
2066 if (trvs::currTask == 0) {
2068 "Check binned-vectors file for reference: %s", save_file.c_str()
2069 );
2070 }
2071 }
2072
2073 trvs::gbytesMem -= trvs::size_in_gb<double>(2*6*binned_vectors.count);
2074 trvs::gbytesMem -= trvs::size_in_gb<int>(binned_vectors.count);
2075
2076 return binned_vectors_sorted;
2077}
2078
2079
2080// -----------------------------------------------------------------------
2081// Binned statistics
2082// -----------------------------------------------------------------------
2083
2085 MeshField& field_a, MeshField& field_b, std::complex<double> shotnoise_amp,
2086 int ell, int m, trv::Binning& kbinning
2087) {
2088 this->resize_stats(kbinning.num_bins);
2089
2090 // Check mesh fields compatibility and reuse methods of the first mesh field.
2091 if (!this->if_fields_compatible(field_a, field_b)) {
2092 if (trvs::currTask == 0) {
2094 "Input mesh fields have incompatible physical properties."
2095 );
2096 }
2098 "Input mesh fields have incompatible physical properties.\n"
2099 );
2100 }
2101
2102 // auto ret_grid_index = [&field_a](int i, int j, int k) {
2103 // return field_a.ret_grid_index(i, j, k);
2104 // };
2105
2106 auto ret_grid_wavevector = [&field_a](int i, int j, int k, double kvec[3]) {
2107 field_a.get_grid_wavevector(i, j, k, kvec);
2108 };
2109
2110 this->compute_shotnoise_aliasing();
2111
2112 std::function<double(int, int, int)> calc_shotnoise_aliasing = [this](
2113 int i, int j, int k
2114 ) {
2115 long long idx_grid = ret_grid_index(i, j, k);
2116 return this->alias_sn[idx_grid];
2117 };
2118
2119 std::function<double(int, int, int)> calc_win_pk, calc_win_sn;
2120 int assignment_order = this->params.assignment_order;
2121 if (this->params.interlace == "true") {
2122 calc_win_pk = [&field_a, &field_b, &assignment_order](
2123 int i, int j, int k
2124 ) {
2125 return
2126 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2127 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2128 };
2129 calc_win_sn = calc_win_pk;
2130 } else
2131 if (this->params.interlace == "false") {
2132#ifndef DBG_FLAG_NOAC
2133 calc_win_sn = calc_shotnoise_aliasing;
2134 calc_win_pk = calc_win_sn;
2135#else // !DBG_FLAG_NOAC
2136 calc_win_pk = [&field_a, &field_b, &assignment_order](
2137 int i, int j, int k
2138 ) {
2139 return
2140 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2141 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2142 };
2143 calc_win_sn = calc_shotnoise_aliasing;
2144#endif // !DBG_FLAG_NOAC
2145 }
2146
2147 // Perform fine binning.
2148 // NOTE: Dynamically allocate owing to size.
2149 // CAVEAT: Discretionary choices such that 0.0 < k < 10.0.
2150 const int n_sample = 1e6;
2151 const double dk_sample = 1.e-5;
2152 if (kbinning.bin_max > n_sample * dk_sample) {
2153 if (trvs::currTask == 0) {
2155 "Input bin range exceeds sampled range. "
2156 "Statistics in bins beyond sampled range are uncomputed."
2157 );
2158 }
2159 }
2160
2161 int* nmodes_sample = new int[n_sample];
2162 double* k_sample = new double[n_sample];
2163 double* pk_sample_real = new double[n_sample];
2164 double* pk_sample_imag = new double[n_sample];
2165 double* sn_sample_real = new double[n_sample];
2166 double* sn_sample_imag = new double[n_sample];
2167 std::complex<double>* pk_sample = new std::complex<double>[n_sample];
2168 std::complex<double>* sn_sample = new std::complex<double>[n_sample];
2169 for (int i = 0; i < n_sample; i++) {
2170 nmodes_sample[i] = 0;
2171 k_sample[i] = 0.;
2172 pk_sample_real[i] = 0.;
2173 pk_sample_imag[i] = 0.;
2174 sn_sample_real[i] = 0.;
2175 sn_sample_imag[i] = 0.;
2176 }
2177
2178 this->reset_stats();
2179
2180#ifdef TRV_USE_OMP
2181#pragma omp parallel for collapse(3)
2182#endif // TRV_USE_OMP
2183 for (int i = 0; i < this->params.ngrid[0]; i++) {
2184 for (int j = 0; j < this->params.ngrid[1]; j++) {
2185 for (int k = 0; k < this->params.ngrid[2]; k++) {
2186 long long idx_grid = ret_grid_index(i, j, k);
2187
2188 double kv[3];
2189 ret_grid_wavevector(i, j, k, kv);
2190
2191 double k_ = trvm::get_vec3d_magnitude(kv);
2192
2193 int idx_k = int(k_ / dk_sample);
2194 if (0 <= idx_k && idx_k < n_sample) {
2195 std::complex<double> fa(field_a[idx_grid][0], field_a[idx_grid][1]);
2196 std::complex<double> fb(field_b[idx_grid][0], field_b[idx_grid][1]);
2197
2198 std::complex<double> pk_mode = fa * std::conj(fb);
2199 std::complex<double> sn_mode =
2200 shotnoise_amp * calc_shotnoise_aliasing(i, j, k);
2201
2202 // Apply grid corrections.
2203 double win_pk = calc_win_pk(i, j, k);
2204 double win_sn = calc_win_sn(i, j, k);
2205
2206 pk_mode /= win_pk;
2207 sn_mode /= win_sn;
2208
2209 // Weight by reduced spherical harmonics.
2210 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
2211 calc_reduced_spherical_harmonic(ell, m, kv);
2212
2213 pk_mode *= ylm;
2214 sn_mode *= ylm;
2215
2216 double pk_mode_real = pk_mode.real();
2217 double pk_mode_imag = pk_mode.imag();
2218 double sn_mode_real = sn_mode.real();
2219 double sn_mode_imag = sn_mode.imag();
2220
2221 // Add contribution.
2222OMP_ATOMIC
2223 nmodes_sample[idx_k]++;
2224OMP_ATOMIC
2225 k_sample[idx_k] += k_;
2226OMP_ATOMIC
2227 pk_sample_real[idx_k] += pk_mode_real;
2228OMP_ATOMIC
2229 pk_sample_imag[idx_k] += pk_mode_imag;
2230OMP_ATOMIC
2231 sn_sample_real[idx_k] += sn_mode_real;
2232OMP_ATOMIC
2233 sn_sample_imag[idx_k] += sn_mode_imag;
2234 }
2235 }
2236 }
2237 }
2238
2239 for (int i = 0; i < n_sample; i++) {
2240 pk_sample[i] = pk_sample_real[i] + trvm::M_I * pk_sample_imag[i];
2241 sn_sample[i] = sn_sample_real[i] + trvm::M_I * sn_sample_imag[i];
2242 }
2243
2244 // Perform binning.
2245 for (int ibin = 0; ibin < kbinning.num_bins; ibin++) {
2246 double k_lower = kbinning.bin_edges[ibin];
2247 double k_upper = kbinning.bin_edges[ibin + 1];
2248 for (int i = 0; i < n_sample; i++) {
2249 double k_ = i * dk_sample;
2250 if (k_lower <= k_ && k_ < k_upper) {
2251 this->nmodes[ibin] += nmodes_sample[i];
2252 this->k[ibin] += k_sample[i];
2253 this->pk[ibin] += pk_sample[i];
2254 this->sn[ibin] += sn_sample[i];
2255 }
2256 }
2257
2258 if (this->nmodes[ibin] != 0) {
2259 this->k[ibin] /= double(this->nmodes[ibin]);
2260 this->pk[ibin] /= double(this->nmodes[ibin]);
2261 this->sn[ibin] /= double(this->nmodes[ibin]);
2262 } else {
2263 this->k[ibin] = kbinning.bin_centres[ibin];
2264 this->pk[ibin] = 0.;
2265 this->sn[ibin] = 0.;
2266 }
2267 }
2268
2269 delete[] nmodes_sample;
2270 delete[] k_sample;
2271 delete[] pk_sample_real;
2272 delete[] pk_sample_imag;
2273 delete[] sn_sample_real;
2274 delete[] sn_sample_imag;
2275 delete[] pk_sample;
2276 delete[] sn_sample;
2277}
2278
2280 MeshField& field_a, MeshField& field_b, std::complex<double> shotnoise_amp,
2281 int ell, int m, trv::Binning& rbinning
2282) {
2283 this->resize_stats(rbinning.num_bins);
2284
2285 // Check mesh fields compatibility and reuse properties and methods of
2286 // the first mesh field.
2287 if (!this->if_fields_compatible(field_a, field_b)) {
2288 if (trvs::currTask == 0) {
2290 "Input mesh fields have incompatible physical properties."
2291 );
2292 }
2294 "Input mesh fields have incompatible physical properties.\n"
2295 );
2296 }
2297
2298 // auto ret_grid_index = [&field_a](int i, int j, int k) {
2299 // return field_a.ret_grid_index(i, j, k);
2300 // };
2301
2302 auto ret_grid_pos_vector = [&field_a](int i, int j, int k, double rvec[3]) {
2303 field_a.get_grid_pos_vector(i, j, k, rvec);
2304 };
2305
2306 this->compute_shotnoise_aliasing();
2307
2308 std::function<double(int, int, int)> calc_shotnoise_aliasing = [this](
2309 int i, int j, int k
2310 ) {
2311 long long idx_grid = ret_grid_index(i, j, k);
2312 return this->alias_sn[idx_grid];
2313 };
2314
2315 std::function<double(int, int, int)> calc_win_pk, calc_win_sn;
2316 int assignment_order = this->params.assignment_order;
2317 if (this->params.interlace == "true") {
2318 calc_win_pk = [&field_a, &field_b, &assignment_order](
2319 int i, int j, int k
2320 ) {
2321 return
2322 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2323 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2324 };
2325 calc_win_sn = calc_win_pk;
2326 } else
2327 if (this->params.interlace == "false") {
2328#ifndef DBG_FLAG_NOAC
2329 calc_win_sn = calc_shotnoise_aliasing;
2330 calc_win_pk = calc_win_sn;
2331#else // !DBG_FLAG_NOAC
2332 calc_win_pk = [&field_a, &field_b, &assignment_order](
2333 int i, int j, int k
2334 ) {
2335 return
2336 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2337 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2338 };
2339 calc_win_sn = calc_shotnoise_aliasing;
2340#endif // !DBG_FLAG_NOAC
2341 }
2342
2343// The nested for-loops below cover all grid point computations so this
2344// is redundant.
2345// // Set up 3-d two-point statistics mesh grids (before inverse
2346// // Fourier transform).
2347// #ifdef TRV_USE_OMP
2348// #pragma omp parallel for simd
2349// #endif // TRV_USE_OMP
2350// for (long long gid = 0; gid < this->params.nmesh; gid++) {
2351// this->twopt_3d[gid][0] = 0.;
2352// this->twopt_3d[gid][1] = 0.;
2353// } // likely redundant but safe
2354
2355 // Compute shot noise--subtracted mode powers on mesh grids.
2356 for (int i = 0; i < this->params.ngrid[0]; i++) {
2357 for (int j = 0; j < this->params.ngrid[1]; j++) {
2358 for (int k = 0; k < this->params.ngrid[2]; k++) {
2359 long long idx_grid = ret_grid_index(i, j, k);
2360
2361 std::complex<double> fa(field_a[idx_grid][0], field_a[idx_grid][1]);
2362 std::complex<double> fb(field_b[idx_grid][0], field_b[idx_grid][1]);
2363
2364 std::complex<double> pk_mode = fa * std::conj(fb);
2365 std::complex<double> sn_mode =
2366 shotnoise_amp * calc_shotnoise_aliasing(i, j, k);
2367
2368 // Apply grid corrections.
2369 double win_pk = calc_win_pk(i, j, k);
2370 double win_sn = calc_win_sn(i, j, k);
2371
2372 pk_mode /= win_pk;
2373 sn_mode /= win_sn;
2374
2375 pk_mode -= sn_mode;
2376
2377 this->twopt_3d[idx_grid][0] = pk_mode.real() / this->vol;
2378 this->twopt_3d[idx_grid][1] = pk_mode.imag() / this->vol;
2379 }
2380 }
2381 }
2382
2383 // Inverse Fourier transform.
2384 if (this->plan_ini) {
2385 fftw_execute(this->inv_transform);
2386 } else {
2387 fftw_execute_dft(field_a.inv_transform, twopt_3d, twopt_3d);
2388 }
2389 trvs::count_ifft += 1;
2390
2391 // Perform fine binning.
2392 // NOTE: Dynamically allocate owing to size.
2393 // CAVEAT: Discretionary choices such that 0 < r < 100k.
2394 const int n_sample = 1e6;
2395 const double dr_sample = 1.e-1;
2396 if (rbinning.bin_max > n_sample * dr_sample) {
2397 if (trvs::currTask == 0) {
2399 "Input bin range exceeds sampled range. "
2400 "Statistics in bins beyond sampled range are uncomputed."
2401 );
2402 }
2403 }
2404
2405 int* npairs_sample = new int[n_sample];
2406 double* r_sample = new double[n_sample];
2407 double* xi_sample_real = new double[n_sample];
2408 double* xi_sample_imag = new double[n_sample];
2409 std::complex<double>* xi_sample = new std::complex<double>[n_sample];
2410 for (int i = 0; i < n_sample; i++) {
2411 npairs_sample[i] = 0;
2412 r_sample[i] = 0.;
2413 xi_sample_real[i] = 0.;
2414 xi_sample_imag[i] = 0.;
2415 }
2416
2417 this->reset_stats();
2418
2419#ifdef TRV_USE_OMP
2420#pragma omp parallel for collapse(3)
2421#endif // TRV_USE_OMP
2422 for (int i = 0; i < this->params.ngrid[0]; i++) {
2423 for (int j = 0; j < this->params.ngrid[1]; j++) {
2424 for (int k = 0; k < this->params.ngrid[2]; k++) {
2425 long long idx_grid = ret_grid_index(i, j, k);
2426
2427 double rv[3];
2428 ret_grid_pos_vector(i, j, k, rv);
2429
2430 double r_ = trvm::get_vec3d_magnitude(rv);
2431
2432 int idx_r = int(r_ / dr_sample);
2433 if (0 <= idx_r && idx_r < n_sample) {
2434 std::complex<double> xi_pair(
2435 this->twopt_3d[idx_grid][0], this->twopt_3d[idx_grid][1]
2436 );
2437
2438 // Weight by reduced spherical harmonics.
2439 std::complex<double> ylm = trvm::SphericalHarmonicCalculator::
2440 calc_reduced_spherical_harmonic(ell, m, rv);
2441
2442 xi_pair *= ylm;
2443
2444 double xi_pair_real = xi_pair.real();
2445 double xi_pair_imag = xi_pair.imag();
2446
2447 // Add contribution.
2448OMP_ATOMIC
2449 npairs_sample[idx_r]++;
2450OMP_ATOMIC
2451 r_sample[idx_r] += r_;
2452OMP_ATOMIC
2453 xi_sample_real[idx_r] += xi_pair_real;
2454OMP_ATOMIC
2455 xi_sample_imag[idx_r] += xi_pair_imag;
2456 }
2457 }
2458 }
2459 }
2460
2461 for (int i = 0; i < n_sample; i++) {
2462 xi_sample[i] = xi_sample_real[i] + trvm::M_I * xi_sample_imag[i];
2463 }
2464
2465 // Perform binning.
2466 for (int ibin = 0; ibin < rbinning.num_bins; ibin++) {
2467 double r_lower = rbinning.bin_edges[ibin];
2468 double r_upper = rbinning.bin_edges[ibin + 1];
2469 for (int i = 0; i < n_sample; i++) {
2470 double r_ = i * dr_sample;
2471 if (r_lower <= r_ && r_ < r_upper) {
2472 this->npairs[ibin] += npairs_sample[i];
2473 this->r[ibin] += r_sample[i];
2474 this->xi[ibin] += xi_sample[i];
2475 }
2476 }
2477
2478 if (this->npairs[ibin] != 0) {
2479 this->r[ibin] /= double(this->npairs[ibin]);
2480 this->xi[ibin] /= double(this->npairs[ibin]);
2481 // this->npairs[ibin] /= 2; // reality condition
2482 } else {
2483 this->r[ibin] = rbinning.bin_centres[ibin];
2484 this->xi[ibin] = 0.;
2485 }
2486 }
2487
2488 delete[] npairs_sample;
2489 delete[] r_sample;
2490 delete[] xi_sample_real;
2491 delete[] xi_sample_imag;
2492 delete[] xi_sample;
2493}
2494
2496 MeshField& field_a, MeshField& field_b,
2497 std::vector< std::complex<double> >& ylm_a,
2498 std::vector< std::complex<double> >& ylm_b,
2499 std::complex<double> shotnoise_amp,
2500 trv::Binning& rbinning
2501) {
2502 if (trvs::currTask == 0) {
2503 trvs::logger.debug("Computing uncoupled shot noise for 3PCF.");
2504 }
2505
2506 this->resize_stats(rbinning.num_bins);
2507
2508 // Check mesh fields compatibility and reuse properties and methods of
2509 // the first mesh field.
2510 if (!this->if_fields_compatible(field_a, field_b)) {
2511 if (trvs::currTask == 0) {
2513 "Input mesh fields have incompatible physical properties."
2514 );
2515 }
2517 "Input mesh fields have incompatible physical properties.\n"
2518 );
2519 }
2520
2521 // auto ret_grid_index = [&field_a](int i, int j, int k) {
2522 // return field_a.ret_grid_index(i, j, k);
2523 // };
2524
2525 auto ret_grid_pos_vector = [&field_a](int i, int j, int k, double rvec[3]) {
2526 field_a.get_grid_pos_vector(i, j, k, rvec);
2527 };
2528
2529 this->compute_shotnoise_aliasing();
2530
2531 std::function<double(int, int, int)> calc_shotnoise_aliasing = [this](
2532 int i, int j, int k
2533 ) {
2534 long long idx_grid = ret_grid_index(i, j, k);
2535 return this->alias_sn[idx_grid];
2536 };
2537
2538 std::function<double(int, int, int)> calc_win_pk, calc_win_sn;
2539 int assignment_order = this->params.assignment_order;
2540 if (this->params.interlace == "true") {
2541 calc_win_pk = [&field_a, &field_b, &assignment_order](
2542 int i, int j, int k
2543 ) {
2544 return
2545 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2546 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2547 };
2548 calc_win_sn = calc_win_pk;
2549 } else
2550 if (this->params.interlace == "false") {
2551#ifndef DBG_FLAG_NOAC
2552 calc_win_sn = calc_shotnoise_aliasing;
2553 calc_win_pk = calc_win_sn;
2554#else // !DBG_FLAG_NOAC
2555 calc_win_pk = [&field_a, &field_b, &assignment_order](
2556 int i, int j, int k
2557 ) {
2558 return
2559 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2560 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2561 };
2562 calc_win_sn = calc_shotnoise_aliasing;
2563#endif // !DBG_FLAG_NOAC
2564 }
2565
2566// The nested for-loops below cover all grid point computations so this
2567// is redundant.
2568// // Set up 3-d two-point statistics mesh grids (before inverse
2569// // Fourier transform).
2570// #ifdef TRV_USE_OMP
2571// #pragma omp parallel for simd
2572// #endif // TRV_USE_OMP
2573// for (long long gid = 0; gid < this->params.nmesh; gid++) {
2574// this->twopt_3d[gid][0] = 0.;
2575// this->twopt_3d[gid][1] = 0.;
2576// } // likely redundant but safe
2577
2578 // Compute meshed statistics.
2579#ifdef TRV_USE_OMP
2580#pragma omp parallel for collapse(3)
2581#endif // TRV_USE_OMP
2582 for (int i = 0; i < this->params.ngrid[0]; i++) {
2583 for (int j = 0; j < this->params.ngrid[1]; j++) {
2584 for (int k = 0; k < this->params.ngrid[2]; k++) {
2585 long long idx_grid = ret_grid_index(i, j, k);
2586
2587 std::complex<double> fa(field_a[idx_grid][0], field_a[idx_grid][1]);
2588 std::complex<double> fb(field_b[idx_grid][0], field_b[idx_grid][1]);
2589
2590 std::complex<double> pk_mode = fa * std::conj(fb);
2591 std::complex<double> sn_mode =
2592 shotnoise_amp * calc_shotnoise_aliasing(i, j, k);
2593
2594 // Apply grid corrections.
2595 double win_pk = calc_win_pk(i, j, k);
2596 double win_sn = calc_win_sn(i, j, k);
2597
2598 pk_mode /= win_pk;
2599 sn_mode /= win_sn;
2600
2601 pk_mode -= sn_mode;
2602
2603 this->twopt_3d[idx_grid][0] = pk_mode.real() / this->vol;
2604 this->twopt_3d[idx_grid][1] = pk_mode.imag() / this->vol;
2605 }
2606 }
2607 }
2608
2609 // Inverse Fourier transform.
2610 if (this->plan_ini) {
2611 fftw_execute(this->inv_transform);
2612 } else {
2613 fftw_execute_dft(field_a.inv_transform, twopt_3d, twopt_3d);
2614 }
2615 trvs::count_ifft += 1;
2616
2617 // Perform fine binning.
2618 // NOTE: Dynamically allocate owing to size.
2619 // CAVEAT: Discretionary choices such that 0 < r < 100k.
2620 const int n_sample = 1e5;
2621 const double dr_sample = 1.;
2622
2623 int* npairs_sample = new int[n_sample];
2624 double* r_sample = new double[n_sample];
2625 double* xi_sample_real = new double[n_sample];
2626 double* xi_sample_imag = new double[n_sample];
2627 std::complex<double>* xi_sample = new std::complex<double>[n_sample];
2628 for (int i = 0; i < n_sample; i++) {
2629 npairs_sample[i] = 0;
2630 r_sample[i] = 0.;
2631 xi_sample_real[i] = 0.;
2632 xi_sample_imag[i] = 0.;
2633 }
2634
2635 this->reset_stats();
2636
2637#ifdef TRV_USE_OMP
2638#pragma omp parallel for collapse(3)
2639#endif // TRV_USE_OMP
2640 for (int i = 0; i < this->params.ngrid[0]; i++) {
2641 for (int j = 0; j < this->params.ngrid[1]; j++) {
2642 for (int k = 0; k < this->params.ngrid[2]; k++) {
2643 long long idx_grid = ret_grid_index(i, j, k);
2644
2645 double rv[3];
2646 ret_grid_pos_vector(i, j, k, rv);
2647
2648 double r_ = trvm::get_vec3d_magnitude(rv);
2649
2650 int idx_r = int(r_ / dr_sample);
2651 if (0 <= idx_r && idx_r < n_sample) {
2652 std::complex<double> xi_pair(
2653 this->twopt_3d[idx_grid][0], this->twopt_3d[idx_grid][1]
2654 );
2655
2656 // Weight by reduced spherical harmonics.
2657 xi_pair *= ylm_a[idx_grid] * ylm_b[idx_grid];
2658
2659 double xi_pair_real = xi_pair.real();
2660 double xi_pair_imag = xi_pair.imag();
2661
2662 // Add contribution.
2663OMP_ATOMIC
2664 npairs_sample[idx_r]++;
2665OMP_ATOMIC
2666 r_sample[idx_r] += r_;
2667OMP_ATOMIC
2668 xi_sample_real[idx_r] += xi_pair_real;
2669OMP_ATOMIC
2670 xi_sample_imag[idx_r] += xi_pair_imag;
2671 }
2672 }
2673 }
2674 }
2675
2676 for (int i = 0; i < n_sample; i++) {
2677 xi_sample[i] = xi_sample_real[i] + trvm::M_I * xi_sample_imag[i];
2678 }
2679
2680 // Perform binning.
2681 for (int ibin = 0; ibin < rbinning.num_bins; ibin++) {
2682 double r_lower = rbinning.bin_edges[ibin];
2683 double r_upper = rbinning.bin_edges[ibin + 1];
2684 for (int i = 0; i < n_sample; i++) {
2685 double r_ = i * dr_sample;
2686 if (r_lower <= r_ && r_ < r_upper) {
2687 this->npairs[ibin] += npairs_sample[i];
2688 this->r[ibin] += r_sample[i];
2689 this->xi[ibin] += xi_sample[i];
2690 }
2691 }
2692
2693 if (this->npairs[ibin] != 0) {
2694 this->r[ibin] /= double(this->npairs[ibin]);
2695 this->xi[ibin] /= double(this->npairs[ibin]);
2696 } else {
2697 this->r[ibin] = rbinning.bin_centres[ibin];
2698 this->xi[ibin] = 0.;
2699 }
2700 }
2701
2702 // Apply normalisation factors.
2703 double norm_factors = 1 / this->vol_cell
2704 * std::pow(-1, this->params.ell1 + this->params.ell2);
2705
2706 for (int ibin = 0; ibin < rbinning.num_bins; ibin++) {
2707 if (this->npairs[ibin] != 0) {
2708 this->xi[ibin] *= norm_factors / double(this->npairs[ibin]);
2709 // this->npairs[ibin] /= 2; // reality condition
2710 }
2711 }
2712
2713 delete[] npairs_sample;
2714 delete[] r_sample;
2715 delete[] xi_sample_real;
2716 delete[] xi_sample_imag;
2717 delete[] xi_sample;
2718}
2719
2720std::complex<double> \
2721FieldStats::compute_uncoupled_shotnoise_for_bispec_per_bin(
2722 MeshField& field_a, MeshField& field_b,
2723 std::vector< std::complex<double> >& ylm_a,
2724 std::vector< std::complex<double> >& ylm_b,
2726 std::complex<double> shotnoise_amp,
2727 double k_a, double k_b
2728) {
2729 if (trvs::currTask == 0) {
2731 "Computing uncoupled shot noise for bispectrum "
2732 "in wavenumber bin [%f, %f).",
2733 k_a, k_b
2734 );
2735 }
2736
2737 // Check mesh fields compatibility and reuse properties and methods of
2738 // the first mesh field.
2739 if (!this->if_fields_compatible(field_a, field_b)) {
2740 if (trvs::currTask == 0) {
2742 "Input mesh fields have incompatible physical properties."
2743 );
2744 }
2746 "Input mesh fields have incompatible physical properties.\n"
2747 );
2748 }
2749
2750 // auto ret_grid_index = [&field_a](int i, int j, int k) {
2751 // return field_a.ret_grid_index(i, j, k);
2752 // };
2753
2754 auto ret_grid_pos_vector = [&field_a](int i, int j, int k, double rvec[3]) {
2755 field_a.get_grid_pos_vector(i, j, k, rvec);
2756 };
2757
2758 this->compute_shotnoise_aliasing();
2759
2760 std::function<double(int, int, int)> calc_shotnoise_aliasing = [this](
2761 int i, int j, int k
2762 ) {
2763 long long idx_grid = ret_grid_index(i, j, k);
2764 return this->alias_sn[idx_grid];
2765 };
2766
2767 std::function<double(int, int, int)> calc_win_pk, calc_win_sn;
2768 int assignment_order = this->params.assignment_order;
2769 if (this->params.interlace == "true") {
2770 calc_win_pk = [&field_a, &field_b, &assignment_order](
2771 int i, int j, int k
2772 ) {
2773 return
2774 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2775 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2776 };
2777 calc_win_sn = calc_win_pk;
2778 } else
2779 if (this->params.interlace == "false") {
2780#ifndef DBG_FLAG_NOAC
2781 calc_win_sn = calc_shotnoise_aliasing;
2782 calc_win_pk = calc_win_sn;
2783#else // !DBG_FLAG_NOAC
2784 calc_win_pk = [&field_a, &field_b, &assignment_order](
2785 int i, int j, int k
2786 ) {
2787 return
2788 field_a.calc_assignment_window_in_fourier(i, j, k, assignment_order)
2789 * field_b.calc_assignment_window_in_fourier(i, j, k, assignment_order);
2790 };
2791 calc_win_sn = calc_shotnoise_aliasing;
2792#endif // !DBG_FLAG_NOAC
2793 }
2794
2795// The nested for-loops below cover all grid point computations so this
2796// is redundant.
2797// // Set up 3-d two-point statistics mesh grids (before inverse
2798// // Fourier transform).
2799// #ifdef TRV_USE_OMP
2800// #pragma omp parallel for simd
2801// #endif // TRV_USE_OMP
2802// for (long long gid = 0; gid < this->params.nmesh; gid++) {
2803// this->twopt_3d[gid][0] = 0.;
2804// this->twopt_3d[gid][1] = 0.;
2805// } // likely redundant but safe
2806
2807 // Compute meshed statistics.
2808#ifdef TRV_USE_OMP
2809#pragma omp parallel for collapse(3)
2810#endif // TRV_USE_OMP
2811 for (int i = 0; i < this->params.ngrid[0]; i++) {
2812 for (int j = 0; j < this->params.ngrid[1]; j++) {
2813 for (int k = 0; k < this->params.ngrid[2]; k++) {
2814 long long idx_grid = ret_grid_index(i, j, k);
2815
2816 std::complex<double> fa(field_a[idx_grid][0], field_a[idx_grid][1]);
2817 std::complex<double> fb(field_b[idx_grid][0], field_b[idx_grid][1]);
2818
2819 std::complex<double> pk_mode = fa * std::conj(fb);
2820 std::complex<double> sn_mode =
2821 shotnoise_amp * calc_shotnoise_aliasing(i, j, k);
2822
2823 // Apply grid corrections.
2824 double win_pk = calc_win_pk(i, j, k);
2825 double win_sn = calc_win_sn(i, j, k);
2826
2827 pk_mode /= win_pk;
2828 sn_mode /= win_sn;
2829
2830 pk_mode -= sn_mode;
2831
2832 this->twopt_3d[idx_grid][0] = pk_mode.real() / this->vol;
2833 this->twopt_3d[idx_grid][1] = pk_mode.imag() / this->vol;
2834 }
2835 }
2836 }
2837
2838 // Inverse Fourier transform.
2839 if (this->plan_ini) {
2840 fftw_execute(this->inv_transform);
2841 } else {
2842 fftw_execute_dft(field_a.inv_transform, twopt_3d, twopt_3d);
2843 }
2844 trvs::count_ifft += 1;
2845
2846 // Weight by spherical Bessel functions and harmonics before summing
2847 // over the configuration-space grids.
2848 double S_ij_k_real = 0., S_ij_k_imag = 0.;
2849
2850#ifdef TRV_USE_OMP
2851#pragma omp parallel reduction(+:S_ij_k_real, S_ij_k_imag)
2852#endif // TRV_USE_OMP
2853{
2854 // Create thread-private copies of the spherical Bessel function calculator.
2855 trvm::SphericalBesselCalculator sj_a_thread(sj_a);
2856 trvm::SphericalBesselCalculator sj_b_thread(sj_b);
2857
2858#ifdef TRV_USE_OMP
2859#pragma omp for collapse(3)
2860#endif // TRV_USE_OMP
2861 for (int i = 0; i < this->params.ngrid[0]; i++) {
2862 for (int j = 0; j < this->params.ngrid[1]; j++) {
2863 for (int k = 0; k < this->params.ngrid[2]; k++) {
2864 long long idx_grid = ret_grid_index(i, j, k);
2865
2866 double rv[3];
2867 ret_grid_pos_vector(i, j, k, rv);
2868
2869 double r_ = trvm::get_vec3d_magnitude(rv);
2870
2871 double ja = sj_a_thread.eval(k_a * r_);
2872 double jb = sj_b_thread.eval(k_b * r_);
2873
2874 std::complex<double> S_ij_k_3d(
2875 this->twopt_3d[idx_grid][0], this->twopt_3d[idx_grid][1]
2876 );
2877
2878 S_ij_k_3d *= ja * jb * ylm_a[idx_grid] * ylm_b[idx_grid];
2879
2880 double S_ij_k_3d_real = S_ij_k_3d.real();
2881 double S_ij_k_3d_imag = S_ij_k_3d.imag();
2882
2883 S_ij_k_real += S_ij_k_3d_real;
2884 S_ij_k_imag += S_ij_k_3d_imag;
2885 }
2886 }
2887 }
2888}
2889
2890 std::complex<double> S_ij_k(S_ij_k_real, S_ij_k_imag);
2891
2892 S_ij_k *= this->vol_cell;
2893
2894 return S_ij_k;
2895}
2896
2897
2898// -----------------------------------------------------------------------
2899// Sampling corrections
2900// -----------------------------------------------------------------------
2901
2902std::function<double(int, int, int)> FieldStats::ret_calc_shotnoise_aliasing()
2903{
2904 if (this->params.assignment == "ngp") {
2905 return [this](int i, int j, int k) {
2906 return calc_shotnoise_aliasing_ngp(i, j, k);
2907 };
2908 }
2909 if (this->params.assignment == "cic") {
2910 return [this](int i, int j, int k) {
2911 return calc_shotnoise_aliasing_cic(i, j, k);
2912 };
2913 }
2914 if (this->params.assignment == "tsc") {
2915 return [this](int i, int j, int k) {
2916 return calc_shotnoise_aliasing_tsc(i, j, k);
2917 };
2918 }
2919 if (this->params.assignment == "pcs") {
2920 return [this](int i, int j, int k) {
2921 return calc_shotnoise_aliasing_pcs(i, j, k);
2922 };
2923 }
2924
2925 if (trvs::currTask == 0) {
2927 "Invalid assignment scheme: '%s'.", this->params.assignment.c_str()
2928 );
2929 }
2931 "Invalid assignment scheme: '%s'.\n", this->params.assignment.c_str()
2932 );
2933}
2934
2935void FieldStats::get_shotnoise_aliasing_sin2(
2936 int i, int j, int k, double& cx2, double& cy2, double& cz2
2937) {
2938 this->shift_grid_indices_fourier(i, j, k);
2939
2940 double u_x = M_PI * i / double(this->params.ngrid[0]);
2941 double u_y = M_PI * j / double(this->params.ngrid[1]);
2942 double u_z = M_PI * k / double(this->params.ngrid[2]);
2943
2944 cx2 = (i != 0) ? std::sin(u_x) * std::sin(u_x) : 0.;
2945 cy2 = (j != 0) ? std::sin(u_y) * std::sin(u_y) : 0.;
2946 cz2 = (k != 0) ? std::sin(u_z) * std::sin(u_z) : 0.;
2947}
2948
2949double FieldStats::calc_shotnoise_aliasing_ngp(int i, int j, int k) {
2950 return 1.;
2951}
2952
2953double FieldStats::calc_shotnoise_aliasing_cic(int i, int j, int k) {
2954 double cx2, cy2, cz2;
2955 this->get_shotnoise_aliasing_sin2(i, j, k, cx2, cy2, cz2);
2956
2957 return (1. - 2./3. * cx2) * (1. - 2./3. * cy2) * (1. - 2./3. * cz2);
2958}
2959
2960double FieldStats::calc_shotnoise_aliasing_tsc(int i, int j, int k) {
2961 double cx2, cy2, cz2;
2962 this->get_shotnoise_aliasing_sin2(i, j, k, cx2, cy2, cz2);
2963
2964 return (1. - cx2 + 2./15. * cx2 * cx2)
2965 * (1. - cy2 + 2./15. * cy2 * cy2)
2966 * (1. - cz2 + 2./15. * cz2 * cz2);
2967}
2968
2969double FieldStats::calc_shotnoise_aliasing_pcs(int i, int j, int k) {
2970 double cx2, cy2, cz2;
2971 this->get_shotnoise_aliasing_sin2(i, j, k, cx2, cy2, cz2);
2972
2973 return (1. - 4./3. * cx2 + 2./5. * cx2 * cx2 - 4./315. * cx2 * cx2 * cx2)
2974 * (1. - 4./3. * cy2 + 2./5. * cy2 * cy2 - 4./315. * cy2 * cy2 * cy2)
2975 * (1. - 4./3. * cz2 + 2./5. * cz2 * cz2 - 4./315. * cz2 * cz2 * cz2);
2976}
2977
2978void FieldStats::compute_shotnoise_aliasing() {
2979 if (this->alias_ini) {return;} // if computed already
2980
2981 if (trvs::currTask == 0) {
2983 "Computing shot noise aliasing function in Fourier space "
2984 "for assignment order %d.",
2985 this->params.assignment_order
2986 );
2987 }
2988
2989 this->alias_sn.resize(this->params.nmesh, 0.); // initialise
2990
2991 trvs::count_rgrid += 1;
2992 trvs::count_grid += .5;
2996
2997 std::function<double(int, int, int)> calc_shotnoise_aliasing =
2998 this->ret_calc_shotnoise_aliasing();
2999
3000#ifdef TRV_USE_OMP
3001#pragma omp parallel for collapse(3)
3002#endif // TRV_USE_OMP
3003 for (int i = 0; i < this->params.ngrid[0]; i++) {
3004 for (int j = 0; j < this->params.ngrid[1]; j++) {
3005 for (int k = 0; k < this->params.ngrid[2]; k++) {
3006 long long idx_grid = ret_grid_index(i, j, k);
3007 this->alias_sn[idx_grid] = calc_shotnoise_aliasing(i, j, k);
3008 }
3009 }
3010 }
3011
3012 this->alias_ini = true; // set aliasing flag
3013}
3014
3015} // 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
std::string space
coordinate space
Definition dataobjs.hpp:58
double bin_max
highest bin edge
Definition dataobjs.hpp:61
int num_bins
number of bins
Definition dataobjs.hpp:62
std::vector< std::complex< double > > sn
shot-noise power in bins
Definition field.hpp:557
void reset_stats()
Reset two-point statistics.
Definition field.cpp:1821
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
~FieldStats()
Destruct two-point statistics.
Definition field.cpp:1805
std::vector< int > nmodes
number of wavevector modes in bins
Definition field.hpp:552
FieldStats(trv::ParameterSet &params, bool plan_ini=true)
Construct pseudo two-point statistics.
Definition field.cpp:1763
std::vector< double > k
average wavenumber in bins
Definition field.hpp:554
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
trv::BinnedVectors record_binned_vectors(trv::Binning &binning, const std::string &save_file)
Record binned vectors given a binning scheme.
Definition field.cpp:1890
void compute_ylm_wgtd_2pt_stats_in_config(MeshField &field_a, MeshField &field_b, std::complex< double > shotnoise_amp, int ell, int m, trv::Binning &rbinning)
Compute binned two-point statistics in configuration space.
Definition field.cpp:2279
Discretely sampled field on a mesh grid from particle catalogues.
Definition field.hpp:68
trv::ParameterSet params
parameter set
Definition field.hpp:70
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
std::string name
field name
Definition field.hpp:71
void assign_weighted_field_to_mesh(ParticleCatalogue &particles, fftw_complex *weights)
Assign a weighted field to a mesh by interpolation scheme.
Definition field.cpp:427
double calc_grid_based_powlaw_norm(ParticleCatalogue &particles, int order)
Definition field.cpp:1702
void reset_density_field()
(Re-)initialise the complex field (and its shadow) on mesh.
Definition field.cpp:358
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
double dr[3]
grid size in each dimension
Definition field.hpp:73
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
fftw_complex * field
complex field on mesh
Definition field.hpp:72
MeshField(trv::ParameterSet &params, bool plan_ini=true, const std::string &name="mesh-field")
Construct the mesh field.
Definition field.cpp:42
void compute_unweighted_field_fluctuations_insitu(ParticleCatalogue &particles)
Compute the unweighted field fluctuations.
Definition field.cpp:1089
double dk[3]
fundamental wavenumber in each dimension
Definition field.hpp:74
double vol
mesh volume
Definition field.hpp:75
~MeshField()
Destruct the mesh field.
Definition field.cpp:329
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
const fftw_complex & operator[](long long gid)
Return mesh field grid cell value.
Definition field.cpp:382
Parameter set.
long long nmesh
number of mesh grid cells
std::string assignment
mesh assignment scheme: {"ngp", "cic", "tsc" (default), "pcs"}
int i_wa
first order of the wide-angle correction term
std::string fftw_scheme
FFTW scheme: {"estimate", "measure" (default), "patient"}.
double volume
box volume (in Mpc^3/h^3)
int ell1
spherical degree associated with the first wavevector
std::string fftw_wisdom_file_b
backward-transform wisdom file path
std::string interlace
interlacing switch: {"true"/"on", "false"/"off" (default)}
int assignment_order
order of the assignment scheme
std::string fftw_wisdom_file_f
derived FFTW wisdom file paths
int ngrid[3]
grid number in each dimension
double boxsize[3]
box size (in Mpc/h) in each dimension
std::string use_fftw_wisdom
use FFTW wisdom: {"false" (default), <path-to-dir>}
Particle catalogue.
Definition particles.hpp:68
double pos_min[3]
minimum values of particle coordinates
Definition particles.hpp:78
int ntotal
total number of particles
Definition particles.hpp:74
double pos_max[3]
maximum values of particle coordinates
Definition particles.hpp:79
Interpolated spherical Bessel function of the first kind.
Definition maths.hpp:258
double eval(double x)
Evaluate the interpolated function.
Definition maths.cpp:366
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 info(const char *fmt_string,...)
Emit a information-level message.
Definition monitor.cpp:264
void debug(const char *fmt_string,...)
Emit a debugging-level message.
Definition monitor.cpp:246
void error(const char *fmt_string,...)
Emit a warning-level message.
Definition monitor.cpp:282
void warn(const char *fmt_string,...)
Emit a warning-level message.
Definition monitor.cpp:273
void reset_level(LogLevel level)
Reset the logger threshold level.
Definition monitor.cpp:156
Mesh field (with one-point statistics) and pseudo two-point statistics.
std::vector< int > get_sorted_indices(std::vector< int > sorting_vector)
Get the sorted indices.
Definition arrayops.cpp:243
void print_binned_vectors_to_file(std::FILE *fileptr, trv::ParameterSet &params, trv::BinnedVectors &binned_vectors)
Print binned vectors to a file.
Definition io.cpp:303
double get_vec3d_magnitude(std::vector< double > vec)
Return the magnitude of a 3-d vector.
Definition maths.cpp:51
const std::complex< double > M_I
imaginary unit
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
bool fftw_wisdom_b_imported
wisdom import status for backward transform
Definition monitor.cpp:60
bool fftw_wisdom_f_imported
wisdom import status for forward transform
Definition monitor.cpp:59
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_fft
number of FFTs
Definition monitor.cpp:56
int count_cgrid
number of 3-d complex grids
Definition monitor.cpp:50
int count_rgrid
number of 3-d real grids
Definition monitor.cpp:49
int count_ifft
number of IFFTs
Definition monitor.cpp:57
void update_maxcntgrid()
Update the maximum 3-d grid counts.
Definition monitor.cpp:73
Binned vectors.
Definition dataobjs.hpp:164
std::vector< double > vecy
y-components
Definition dataobjs.hpp:171
std::vector< double > vecz
z-components
Definition dataobjs.hpp:172
int count
number of vectors
Definition dataobjs.hpp:165
int num_bins
number of bins
Definition dataobjs.hpp:166
std::vector< double > upper_edges
upper bin edges
Definition dataobjs.hpp:169
std::vector< double > lower_edges
lower bin edges
Definition dataobjs.hpp:168
std::vector< double > vecx
x-components
Definition dataobjs.hpp:170
std::vector< int > indices
bin indices
Definition dataobjs.hpp:167
Line-of-sight vector.
Definition dataobjs.hpp:184
double pos[3]
3-d position vector
Definition dataobjs.hpp:185