Triumvirate C++ API 0.5.0
Three-point clustering measurements in large-scale structure analyses.
Loading...
Searching...
No Matches
particles.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 "particles.hpp"
28
29namespace trvs = trv::sys;
30
31namespace trv {
32
33// ***********************************************************************
34// Life cycle
35// ***********************************************************************
36
38 if (verbose >= 0) {
40 }
41
42 // Set default values (likely redundant but safe).
43 this->pdata = nullptr;
44 this->ntotal = 0;
45 this->wtotal = 0.;
46 this->wstotal = 0.;
47 for (int iaxis = 0; iaxis < 3; iaxis++) {
48 this->pos_min[iaxis] = 0.;
49 this->pos_max[iaxis] = 0.;
50 this->pos_span[iaxis] = 0.;
51 }
52}
53
55
57 // Check the total number of particles.
58 if (num <= 0) {
59 if (trvs::currTask == 0) {
60 trvs::logger.error("Number of particles is non-positive.");
61 }
63 "Number of particles is non-positive.\n"
64 );
65 }
66
67 this->ntotal = num;
68
69 // Renew particle data.
70 this->reset_particles();
71
72 this->pdata = new ParticleData[this->ntotal];
75}
76
80
82 // Free particle data.
83 if (this->pdata != nullptr) {
84 delete[] this->pdata; this->pdata = nullptr;
86 }
87}
88
89
90// ***********************************************************************
91// Operators & reserved methods
92// ***********************************************************************
93
95 return this->pdata[pid];
96}
97
98
99// ***********************************************************************
100// Data I/O
101// ***********************************************************************
102
104 const std::string& catalogue_filepath,
105 const std::string& catalogue_columns,
106 double volume
107) {
108 if (!(this->source.empty())) {
109 if (trvs::currTask == 0) {
111 "Catalogue already loaded from another source: %s.", this->source.c_str()
112 );
113 }
115 "Catalogue already loaded from another source: %s.\n",
116 this->source.c_str()
117 );
118 }
119 this->source = "extfile:" + catalogue_filepath;
120
121 // ---------------------------------------------------------------------
122 // Columns & fields
123 // ---------------------------------------------------------------------
124
125 // CAVEAT: Hard-coded ordered column names.
126 const std::vector<std::string> names_ordered = {
127 "x", "y", "z", "nz", "ws", "wc"
128 };
129
130 std::istringstream iss(catalogue_columns);
131 std::vector<std::string> colnames;
132 std::string name;
133 while (std::getline(iss, name, ',')) {
134 colnames.push_back(name);
135 }
136
137 // CAVEAT: Default -1 index as a flag for unfound column names.
138 std::vector<int> name_indices(names_ordered.size(), -1);
139 for (int iname = 0; iname < int(names_ordered.size()); iname++) {
140 std::ptrdiff_t col_idx = std::distance(
141 colnames.begin(),
142 std::find(colnames.begin(), colnames.end(), names_ordered[iname])
143 );
144 if (0 <= col_idx && col_idx < int(colnames.size())) {
145 name_indices[iname] = col_idx;
146 }
147 }
148
149 // Check for the 'nz' column.
150 if (name_indices[3] == -1) {
151 if (trvs::currTask == 0) {
153 "Catalogue 'nz' field is unavailable and "
154 "will be set to the mean density in the bounding box (source=%s).",
155 this->source.c_str()
156 );
157 }
158 }
159
160 // ---------------------------------------------------------------------
161 // Data reading
162 // ---------------------------------------------------------------------
163
164 std::ifstream fin;
165
166 fin.open(catalogue_filepath.c_str(), std::ios::in);
167
168 if (fin.fail()) {
169 fin.close();
170 if (trvs::currTask == 0) {
171 trvs::logger.error("Failed to open file: %s", this->source.c_str());
172 }
173 throw trvs::IOError("Failed to open file: %s\n", this->source.c_str());
174 }
175
176 // Initialise particle data.
177 int num_lines = 0;
178 std::string line_str;
179 while (std::getline(fin, line_str)) {
180 // Terminate at the end of file.
181 if (!fin) {break;}
182
183 // Skip empty lines or comment lines.
184 if (line_str.empty() || line_str[0] == '#') {continue;}
185
186 // Count the line as valid otherwise.
187 num_lines++;
188 }
189
190 fin.close();
191
192 this->initialise_particles(num_lines);
193
194 // Set particle data.
195 double nz_box_default = 0.;
196 if (volume > 0.) {
197 nz_box_default = this->ntotal / volume;
198 }
199
200 fin.open(catalogue_filepath.c_str(), std::ios::in);
201
202 int idx_line = 0; // current line number
203 double nz, ws, wc; // placeholder variables
204 double entry; // data entry (per column per row)
205 while (std::getline(fin, line_str)) { // std::string line_str;
206 // Terminate at the end of file.
207 if (!fin) {break;}
208
209 // Skip empty lines or comment lines.
210 if (line_str.empty() || line_str[0] == '#') {continue;}
211
212 // Extract row entries.
213 std::vector<double> row;
214
215 std::stringstream ss(
216 line_str, std::ios_base::out | std::ios_base::in | std::ios_base::binary
217 );
218 while (ss >> entry) {row.push_back(entry);}
219
220 // Add the current line as a particle.
221 this->pdata[idx_line].pos[0] = row[name_indices[0]]; // x
222 this->pdata[idx_line].pos[1] = row[name_indices[1]]; // y
223 this->pdata[idx_line].pos[2] = row[name_indices[2]]; // z
224
225 if (name_indices[3] != -1) {
226 nz = row[name_indices[3]];
227 } else {
228 nz = nz_box_default; // default value
229 }
230
231 if (name_indices[4] != -1) {
232 ws = row[name_indices[4]];
233 } else {
234 ws = 1.; // default value
235 }
236
237 if (name_indices[5] != -1) {
238 wc = row[name_indices[5]];
239 } else {
240 wc = 1.; // default value
241 }
242
243 this->pdata[idx_line].nz = nz;
244 this->pdata[idx_line].ws = ws;
245 this->pdata[idx_line].wc = wc;
246 this->pdata[idx_line].w = ws * wc;
247
248 idx_line++;
249 }
250
251 fin.close();
252
253 // ---------------------------------------------------------------------
254 // Catalogue properties
255 // ---------------------------------------------------------------------
256
257 // Calculate total weights.
258 this->calc_total_weights();
259
260 // Calculate particle extents.
261 this->calc_pos_extents();
262
263 return 0;
264}
265
267 std::vector<double> x, std::vector<double> y, std::vector<double> z,
268 std::vector<double> nz, std::vector<double> ws, std::vector<double> wc
269) {
270 this->source = "extdata";
271
272 // Check data array sizes.
273 int ntotal = x.size();
274 if (!(
275 ntotal == int(y.size())
276 && ntotal == int(z.size())
277 && ntotal == int(nz.size())
278 && ntotal == int(ws.size())
279 && ntotal == int(wc.size())
280 )) {
281 if (trvs::currTask == 0) {
283 "Inconsistent particle data dimensions (source=%s).",
284 this->source.c_str()
285 );
286 }
288 "Inconsistent particle data dimensions (source=%s).\n",
289 this->source.c_str()
290 );
291 }
292
293 // Fill in particle data.
294 this->initialise_particles(ntotal);
295
296#ifdef TRV_USE_OMP
297#pragma omp parallel for simd
298#endif // TRV_USE_OMP
299 for (int pid = 0; pid < ntotal; pid++) {
300 this->pdata[pid].pos[0] = x[pid];
301 this->pdata[pid].pos[1] = y[pid];
302 this->pdata[pid].pos[2] = z[pid];
303 this->pdata[pid].nz = nz[pid];
304 this->pdata[pid].ws = ws[pid];
305 this->pdata[pid].wc = wc[pid];
306 this->pdata[pid].w = ws[pid] * wc[pid];
307 }
308
309 // Calculate sample weight sum.
310 this->calc_total_weights();
311
312 // Calculate the extents of particles.
313 this->calc_pos_extents();
314
315 return 0;
316}
317
318
319// ***********************************************************************
320// Catalogue properties
321// ***********************************************************************
322
324 if (this->pdata == nullptr) {
325 if (trvs::currTask == 0) {
326 trvs::logger.error("Particle data are uninitialised.");
327 }
328 throw trvs::InvalidDataError("Particle data are uninitialised.\n");
329 }
330
331 double wtotal = 0., wstotal = 0.;
332
333#ifdef TRV_USE_OMP
334#if defined(__GNUC__) && !defined(__clang__)
335#pragma omp parallel for simd reduction(+:wtotal, wstotal)
336#else // !__GNUC__ || __clang__
337#pragma omp parallel for reduction(+:wtotal, wstotal)
338#endif // __GNUC__ && !__clang__
339#endif // TRV_USE_OMP
340 for (int pid = 0; pid < this->ntotal; pid++) {
341 wtotal += this->pdata[pid].w;
342 wstotal += this->pdata[pid].ws;
343 }
344
345 this->wtotal = wtotal;
346 this->wstotal = wstotal;
347
348 if (trvs::currTask == 0) {
350 "Catalogue loaded: "
351 "ntotal = %d, wtotal = %.3f, wstotal = %.3f (source=%s).",
352 this->ntotal, this->wtotal, this->wstotal, this->source.c_str()
353 );
354 }
355}
356
358 if (this->pdata == nullptr) {
359 if (trvs::currTask == 0) {
360 trvs::logger.error("Particle data are uninitialised.");
361 }
362 throw trvs::InvalidDataError("Particle data are uninitialised.\n");
363 }
364
365 // Initialise minimum and maximum values with the 0th particle's.
366 double pos_min[3], pos_max[3];
367 for (int iaxis = 0; iaxis < 3; iaxis++) {
368 pos_min[iaxis] = this->pdata[0].pos[iaxis];
369 pos_max[iaxis] = this->pdata[0].pos[iaxis];
370 }
371
372 // Update minimum and maximum values partice by particle.
373#ifdef TRV_USE_OMP
374#pragma omp parallel for reduction(min:pos_min) reduction(max:pos_max)
375#endif // TRV_USE_OMP
376 for (int pid = 0; pid < this->ntotal; pid++) {
377 for (int iaxis = 0; iaxis < 3; iaxis++) {
378 pos_min[iaxis] = (pos_min[iaxis] < this->pdata[pid].pos[iaxis]) ?
379 pos_min[iaxis] : this->pdata[pid].pos[iaxis];
380 pos_max[iaxis] = (pos_max[iaxis] > this->pdata[pid].pos[iaxis]) ?
381 pos_max[iaxis] : this->pdata[pid].pos[iaxis];
382 }
383 }
384
385 for (int iaxis = 0; iaxis < 3; iaxis++) {
386 this->pos_min[iaxis] = pos_min[iaxis];
387 this->pos_max[iaxis] = pos_max[iaxis];
388 this->pos_span[iaxis] = pos_max[iaxis] - pos_min[iaxis];
389 }
390
391 if (trvs::currTask == 0 && init) {
393 "Extents of particle coordinates: "
394 "{'x': (%.3f, %.3f | %.3f),"
395 " 'y': (%.3f, %.3f | %.3f),"
396 " 'z': (%.3f, %.3f | %.3f)} "
397 "(source=%s).",
398 this->pos_min[0], this->pos_max[0], this->pos_span[0],
399 this->pos_min[1], this->pos_max[1], this->pos_span[1],
400 this->pos_min[2], this->pos_max[2], this->pos_span[2],
401 this->source.c_str()
402 );
403 }
404}
405
406
407// ***********************************************************************
408// Catalogue operations
409// ***********************************************************************
410
411void ParticleCatalogue::offset_coords(const double dpos[3]) {
412 if (this->pdata == nullptr) {
413 if (trvs::currTask == 0) {
414 trvs::logger.error("Particle data are uninitialised.");
415 }
416 throw trvs::InvalidDataError("Particle data are uninitialised.\n");
417 }
418
419#ifdef TRV_USE_OMP
420#pragma omp parallel for
421#endif // TRV_USE_OMP
422 for (int pid = 0; pid < this->ntotal; pid++) {
423 for (int iaxis = 0; iaxis < 3; iaxis++) {
424 this->pdata[pid].pos[iaxis] -= dpos[iaxis];
425 }
426 }
427
428 this->calc_pos_extents();
429}
430
431void ParticleCatalogue::\
432offset_coords_for_periodicity(const double boxsize[3]) {
433#ifdef TRV_USE_OMP
434#pragma omp parallel for
435#endif // TRV_USE_OMP
436 for (int pid = 0; pid < this->ntotal; pid++) {
437 for (int iaxis = 0; iaxis < 3; iaxis++) {
438 if (this->pdata[pid].pos[iaxis] >= boxsize[iaxis]) {
439 this->pdata[pid].pos[iaxis] = std::fmod(
440 this->pdata[pid].pos[iaxis], boxsize[iaxis]
441 );
442 } else
443 if (this->pdata[pid].pos[iaxis] < 0.) {
444 this->pdata[pid].pos[iaxis] = std::fmod(
445 this->pdata[pid].pos[iaxis], boxsize[iaxis]
446 ) + boxsize[iaxis];
447 }
448 }
449 }
450
451 this->calc_pos_extents();
452}
453
455 ParticleCatalogue& catalogue,
456 const double boxsize[3]
457) {
458 catalogue.calc_pos_extents(false); // likely redundant but safe
459 for (int iaxis = 0; iaxis < 3; iaxis++) {
460 if (catalogue.pos_span[iaxis] > boxsize[iaxis]) {
461 if (trvs::currTask == 0) {
463 "Catalogue extent exceeds the box size along axis %d: "
464 "span = %.3f, boxsize = %.3f (source=%s). "
465 "Some particles may lie outside the box after centring.",
466 iaxis,
467 catalogue.pos_span[iaxis], boxsize[iaxis],
468 catalogue.source.c_str()
469 );
470 }
471 }
472 }
473
474 double xmid = (catalogue.pos_min[0] + catalogue.pos_max[0]) / 2.;
475 double ymid = (catalogue.pos_min[1] + catalogue.pos_max[1]) / 2.;
476 double zmid = (catalogue.pos_min[2] + catalogue.pos_max[2]) / 2.;
477
478 double dvec[3] = {
479 xmid - boxsize[0]/2., ymid - boxsize[1]/2., zmid - boxsize[2]/2.
480 };
481
482 catalogue.offset_coords(dvec);
483}
484
486 ParticleCatalogue& catalogue, ParticleCatalogue& catalogue_ref,
487 const double boxsize[3]
488) {
489 catalogue.calc_pos_extents(false); // likely redundant but safe
490 for (int iaxis = 0; iaxis < 3; iaxis++) {
491 if (catalogue.pos_span[iaxis] > boxsize[iaxis]) {
492 if (trvs::currTask == 0) {
494 "Catalogue extent exceeds the box size along axis %d: "
495 "span = %.3f, boxsize = %.3f (source=%s). "
496 "Some particles may lie outside the box after centring.",
497 iaxis,
498 catalogue.pos_span[iaxis], boxsize[iaxis],
499 catalogue.source.c_str()
500 );
501 }
502 }
503 }
504
505 catalogue_ref.calc_pos_extents(false); // likely redundant but safe
506 for (int iaxis = 0; iaxis < 3; iaxis++) {
507 if (catalogue_ref.pos_span[iaxis] > boxsize[iaxis]) {
508 if (trvs::currTask == 0) {
510 "Catalogue extent exceeds the box size along axis %d: "
511 "span = %.3f, boxsize = %.3f (source=%s). "
512 "Some particles may lie outside the box after centring.",
513 iaxis,
514 catalogue_ref.pos_span[iaxis], boxsize[iaxis],
515 catalogue_ref.source.c_str()
516 );
517 }
518 }
519 }
520
521 double xmid = (catalogue_ref.pos_min[0] + catalogue_ref.pos_max[0]) / 2.;
522 double ymid = (catalogue_ref.pos_min[1] + catalogue_ref.pos_max[1]) / 2.;
523 double zmid = (catalogue_ref.pos_min[2] + catalogue_ref.pos_max[2]) / 2.;
524
525 double dvec[3] = {
526 xmid - boxsize[0]/2., ymid - boxsize[1]/2., zmid - boxsize[2]/2.
527 };
528
529 catalogue_ref.offset_coords(dvec);
530 catalogue.offset_coords(dvec);
531}
532
534 ParticleCatalogue& catalogue,
535 const double boxsize[3], const double boxsize_pad[3]
536) {
537 catalogue.calc_pos_extents(false); // likely redundant but safe
538 for (int iaxis = 0; iaxis < 3; iaxis++) {
539 if (catalogue.pos_span[iaxis] > boxsize[iaxis]) {
540 if (trvs::currTask == 0) {
542 "Catalogue extent exceeds the box size along axis %d: "
543 "span = %.3f, boxsize = %.3f (source=%s). "
544 "Some particles may lie outside the box after padding.",
545 iaxis,
546 catalogue.pos_span[iaxis], boxsize[iaxis],
547 catalogue.source.c_str()
548 );
549 }
550 }
551 }
552
553 double dvec[3] = {
554 catalogue.pos_min[0] - boxsize_pad[0] * boxsize[0],
555 catalogue.pos_min[1] - boxsize_pad[1] * boxsize[1],
556 catalogue.pos_min[2] - boxsize_pad[2] * boxsize[2]
557 };
558
559 catalogue.offset_coords(dvec);
560}
561
563 ParticleCatalogue& catalogue, ParticleCatalogue& catalogue_ref,
564 const double boxsize[3], const double boxsize_pad[3]
565) {
566 catalogue.calc_pos_extents(false); // likely redundant but safe
567 for (int iaxis = 0; iaxis < 3; iaxis++) {
568 if (catalogue.pos_span[iaxis] > boxsize[iaxis]) {
569 if (trvs::currTask == 0) {
571 "Catalogue extent exceeds the box size along axis %d: "
572 "span = %.3f, boxsize = %.3f (source=%s). "
573 "Some particles may lie outside the box after padding.",
574 iaxis,
575 catalogue.pos_span[iaxis], boxsize[iaxis],
576 catalogue.source.c_str()
577 );
578 }
579 }
580 }
581
582 catalogue_ref.calc_pos_extents(false); // likely redundant but safe
583 for (int iaxis = 0; iaxis < 3; iaxis++) {
584 if (catalogue_ref.pos_span[iaxis] > boxsize[iaxis]) {
585 if (trvs::currTask == 0) {
587 "Catalogue extent exceeds the box size along axis %d: "
588 "span = %.3f, boxsize = %.3f (source=%s). "
589 "Some particles may lie outside the box after padding.",
590 iaxis,
591 catalogue_ref.pos_span[iaxis], boxsize[iaxis],
592 catalogue_ref.source.c_str()
593 );
594 }
595 }
596 }
597
598 double dvec[3] = {
599 catalogue_ref.pos_min[0] - boxsize_pad[0] * boxsize[0],
600 catalogue_ref.pos_min[1] - boxsize_pad[1] * boxsize[1],
601 catalogue_ref.pos_min[2] - boxsize_pad[2] * boxsize[2]
602 };
603
604 catalogue_ref.offset_coords(dvec);
605 catalogue.offset_coords(dvec);
606}
607
609 ParticleCatalogue& catalogue,
610 const double boxsize[3], const int ngrid[3], const double ngrid_pad[3]
611) {
612 catalogue.calc_pos_extents(false); // likely redundant but safe
613
614 double dvec[3] = {
615 catalogue.pos_min[0], catalogue.pos_min[1], catalogue.pos_min[2]
616 };
617
618 dvec[0] -= ngrid_pad[0] * boxsize[0] / double(ngrid[0]);
619 dvec[1] -= ngrid_pad[1] * boxsize[1] / double(ngrid[1]);
620 dvec[2] -= ngrid_pad[2] * boxsize[2] / double(ngrid[2]);
621
622 catalogue.offset_coords(dvec);
623}
624
626 ParticleCatalogue& catalogue, ParticleCatalogue& catalogue_ref,
627 const double boxsize[3], const int ngrid[3], const double ngrid_pad[3]
628) {
629 catalogue_ref.calc_pos_extents(false); // likely redundant but safe
630
631 double dvec[3] = {
632 catalogue_ref.pos_min[0] - ngrid_pad[0] * boxsize[0] / double(ngrid[0]),
633 catalogue_ref.pos_min[1] - ngrid_pad[1] * boxsize[1] / double(ngrid[1]),
634 catalogue_ref.pos_min[2] - ngrid_pad[2] * boxsize[2] / double(ngrid[2])
635 };
636
637 catalogue_ref.offset_coords(dvec);
638 catalogue.offset_coords(dvec);
639}
640
641} // namespace trv
Particle catalogue.
Definition particles.hpp:68
void initialise_particles(const int num)
Initialise particle data container.
Definition particles.cpp:56
static void centre_in_box(ParticleCatalogue &catalogue, const double boxsize[3])
Centre a catalogue in a box.
int load_particle_data(std::vector< double > x, std::vector< double > y, std::vector< double > z, std::vector< double > nz, std::vector< double > ws, std::vector< double > wc)
Read in particle data.
void calc_pos_extents(bool init=true)
Calculate the extents of particle positions.
~ParticleCatalogue()
Destruct the particle catalogue.
Definition particles.cpp:54
ParticleCatalogue(int verbose=-1)
Construct the particle catalogue with initial values.
Definition particles.cpp:37
std::string source
catalogue source
Definition particles.hpp:70
void reset_particles()
Reset particle data container.
Definition particles.cpp:81
void calc_total_weights()
Calculate total overall weight of particles.
ParticleData & operator[](const int pid)
Return individual particle information.
Definition particles.cpp:94
void offset_coords(const double dpos[3])
Offset particle positions by a given vector.
void finalise_particles()
Finalise particle data container.
Definition particles.cpp:77
static void pad_grids(ParticleCatalogue &catalogue, const double boxsize[3], const int ngrid[3], const double ngrid_pad[3])
Pad a catalogue in a box.
double pos_span[3]
span of particle coordinates
Definition particles.hpp:80
ParticleData * pdata
particle data
Definition particles.hpp:72
int load_catalogue_file(const std::string &catalogue_filepath, const std::string &catalogue_columns, double volume=0.)
Read in a catalogue file.
double pos_min[3]
minimum values of particle coordinates
Definition particles.hpp:78
int ntotal
total number of particles
Definition particles.hpp:74
static void pad_in_box(ParticleCatalogue &catalogue, const double boxsize[3], const double boxsize_pad[3])
Pad a catalogue in a box.
double wtotal
total overall weight of particles
Definition particles.hpp:75
double wstotal
total sample weight of particles
Definition particles.hpp:76
double pos_max[3]
maximum values of particle coordinates
Definition particles.hpp:79
Exception raised when an input/output operation fails.
Definition monitor.hpp:408
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 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
double gbytesMem
current memory usage in gibibytes
Definition monitor.cpp:46
double size_in_gb(long long num)
Return size in gibibytes.
Definition monitor.hpp:101
void update_maxmem()
Update the maximum memory usage estimate.
Definition monitor.cpp:68
Logger logger
default logger (at NSET logging level)
int currTask
current task
Definition monitor.cpp:44
Particle containers with I/O methods and operations.
Particle data container.
Definition particles.hpp:53
double pos[3]
particle position vector
Definition particles.hpp:54
double w
particle overall weight
Definition particles.hpp:58
double ws
particle sample weight
Definition particles.hpp:56
double nz
redshift-dependent expected number density
Definition particles.hpp:55
double wc
particle clustering weight
Definition particles.hpp:57