FML
Data Structures | Typedefs | Enumerations | Functions | Variables
FML::FOF Namespace Reference

This namespace deals how to link together particles in groups. More...

Data Structures

class  FoFCell
 A gridcell in a grid used for speeding up the linking of particles The ID of the particles is their position in the particle list. More...
 
class  FoFHalo
 This class defines how to compute the halos from the particles. More...
 

Typedefs

using Timings = FML::UTILS::Timings
 

Enumerations

enum  ParticleType { BOUNDARY_PARTICLE = -1 , REGULAR_PARTICLE = 0 }
 Type of particle for book-keeping. More...
 

Functions

template<class T , int NDIM, class FoFHaloClass = FoFHalo<T, NDIM>>
void FriendsOfFriends (T *part, size_t NumPart, double linking_length, int nmin_FoF_group, bool periodic, double Buffersize_over_Boxsize, std::vector< FoFHaloClass > &LocalFoFGroups, int Ngrid=FoF_Ngrid_max)
 Locate friends of friends groups and bin what you want over these groups. More...
 
template<class T , int NDIM>
void ParticlesToCells (int Ngrid, int &Local_nx, double dx_boundary, T *part, size_t NumPart, T *part_boundary, size_t NumPart_boundary, std::vector< FoFCell > &PartCells)
 Internal method: bin particles to a grid. More...
 

Variables

size_t * particlegriddata {nullptr}
 
const constexpr size_t no_FoF_ID = std::numeric_limits<size_t>::max()
 If a particle belongs to no FoF group it is given this FoF ID. More...
 
int FoF_Ngrid_max = 1024
 The maximum grid we are allowed to use to bin the particle to This is just to have a memory limit in what the algorithm is allowed to allocate. More...
 
Timings foftimer
 Records timings of the FoF search. More...
 

Detailed Description

This namespace deals how to link together particles in groups.

Typedef Documentation

◆ Timings

Definition at line 33 of file FoF.h.

Enumeration Type Documentation

◆ ParticleType

Type of particle for book-keeping.

Enumerator
BOUNDARY_PARTICLE 
REGULAR_PARTICLE 

Definition at line 26 of file FoF.h.

@ BOUNDARY_PARTICLE
Definition: FoF.h:26
@ REGULAR_PARTICLE
Definition: FoF.h:26

Function Documentation

◆ FriendsOfFriends()

template<class T , int NDIM, class FoFHaloClass = FoFHalo<T, NDIM>>
void FML::FOF::FriendsOfFriends ( T *  part,
size_t  NumPart,
double  linking_length,
int  nmin_FoF_group,
bool  periodic,
double  Buffersize_over_Boxsize,
std::vector< FoFHaloClass > &  LocalFoFGroups,
int  Ngrid = FoF_Ngrid_max 
)

Locate friends of friends groups and bin what you want over these groups.

Template Parameters
TThe particle class
NDIMThe number of dimensions
FoFHaloClassThe FoF group class, determined what to be binned up over the FoF group
Parameters
[in]partPointer to particles. If the particle has a set_fofid then we will also store the FoFID in the particles.
[in]NumPartNumber of local particles
[in]linking_lengthThe distance in units of mean particle seperation (in [0,1)) required to have a link between two particles. Typical value is 0.2.
[in]nmin_FoF_groupMinimum number of particles in a FoF group to store it. Typical value is 20.
[in]periodicIs the box periodic?
[in]Buffersize_over_BoxsizeThe buffer we communicate on both sides of the domain in units of the boxsize. Typical value for N-body simulations is (3 Mpc/h) / boxsize.
[out]LocalFoFGroupsThe results: list of FoF groups.
[in]Ngrid(Optional) The maximum gridsize we use to bin the particles to in order to speed up the calculation. The default FoF_Ngrid_max is set in the header. Larger value means more memory needed.

Definition at line 187 of file FoF.h.

194 {
195
196 LocalFoFGroups.clear();
197 if (NumPart == 0)
198 return;
199 const bool debug = false;
200
201 // Some basic checks
202 assert_mpi(FML::PARTICLE::GetNDIM(T()) == NDIM,
203 "FriendsOfFriends::The dimensions of particle and method do not match!\n");
204
205 // Compute fof search distance
206 size_t NumPart_tot = NumPart;
207 FML::SumOverTasks(&NumPart_tot);
208 const double mean_particle_separation = 1.0 / std::pow(NumPart_tot, 1.0 / 3.0);
209 const double fof_distance = linking_length * mean_particle_separation;
210 const double fof_distance2 = fof_distance * fof_distance;
211
212 // Compute grid-size for faster linking
213 if(Ngrid == 0) Ngrid = FoF_Ngrid_max;
214 Ngrid = std::min(int(0.5 / fof_distance), Ngrid);
215
216 // Set how the size of the grid-cube we need to search
217 const constexpr int twotondim = FML::power(2, NDIM);
218
219 // Check that the grid is not too small
220 assert_mpi(1.0 / Ngrid > 2 * fof_distance,
221 "FriendsOfFriends::The gridsize is too large (larger than the 2x linking distance)");
222
223 // Sort particles by x position
224 // This speed it up when doing the linking, typically by ~20-30%
225 foftimer.StartTiming("Sort particles");
226 std::sort(part, part + NumPart, [](const T & a, const T & b) {
227 // The y axis seems fastest in benchmarks
228 constexpr int axis = 1;
229 T * f = const_cast<T *>(&a);
230 T * g = const_cast<T *>(&b);
231 const auto pos_a = FML::PARTICLE::GetPos(*f);
232 const auto pos_b = FML::PARTICLE::GetPos(*g);
233 return pos_b[axis] > pos_a[axis];
234 });
235 foftimer.EndTiming("Sort particles");
236
237 // Compute ranges for which to copy over boundary partices
238 const double xmin = FML::xmin_domain;
239 const double xmax = FML::xmax_domain;
240 const double dx_boundary = FML::NTasks == 1 ? 0.0 : Buffersize_over_Boxsize;
241
242 // If the boundary we ask for is too large
243 if (dx_boundary > xmax - xmin) {
244 throw std::runtime_error("FoF::The boundary asked for is larger than the neighbor task. Reduce "
245 "boundary (or the number of CPUs)\n");
246 }
247
248 //=========================================================================
249 // Fetch all the boundary particles
250 // We gather particles within dx_boundary of each side
251 //=========================================================================
252 std::vector<T> boundary_particles{};
253 const size_t bytes_per_particle = FML::PARTICLE::GetSize(part[0]);
254#ifdef USE_MPI
255 foftimer.StartTiming("Communicate boundary");
256 if (FML::NTasks > 1) {
257 // Count how may particles to send left and right
258 size_t n_send_left = 0;
259 size_t n_send_right = 0;
260 size_t noutside = 0;
261 for (size_t i = 0; i < NumPart; i++) {
262 const auto * pos = FML::PARTICLE::GetPos(part[i]);
263 if (pos[0] < xmin + dx_boundary)
264 ++n_send_left;
265 if (pos[0] > xmax - dx_boundary)
266 ++n_send_right;
267 if (pos[0] < xmin or pos[0] > xmax)
268 noutside++;
269 }
270 if (noutside > 0)
271 throw std::runtime_error("FoF::Particle detected outside local domain, please ensure all particles "
272 "have been moved inside domain\n");
273
274 // The MPI id of the left and right task
275 MPI_Status status;
276 const int RightTask = (FML::ThisTask + 1) % FML::NTasks;
277 const int LeftTask = (FML::ThisTask - 1 + FML::NTasks) % FML::NTasks;
278
279 // Communicate how many to send and get how many to recieve
280 size_t n_recv_left = 0;
281 size_t n_recv_right = 0;
282
283 // Send left recieve from right
284 MPI_Sendrecv(&n_send_left,
285 sizeof(n_send_left),
286 MPI_BYTE,
287 LeftTask,
288 0,
289 &n_recv_right,
290 sizeof(n_recv_right),
291 MPI_BYTE,
292 RightTask,
293 0,
294 MPI_COMM_WORLD,
295 &status);
296 // Send right recieve from left
297 MPI_Sendrecv(&n_send_right,
298 sizeof(n_send_right),
299 MPI_BYTE,
300 RightTask,
301 0,
302 &n_recv_left,
303 sizeof(n_recv_left),
304 MPI_BYTE,
305 LeftTask,
306 0,
307 MPI_COMM_WORLD,
308 &status);
309
310 // If not periodic then left boundary on first task and right boundary on last task is not there
311 if (not periodic) {
312 if (FML::ThisTask == 0)
313 n_recv_left = n_send_left = 0;
314 if (FML::ThisTask == FML::NTasks - 1)
315 n_recv_right = n_send_right = 0;
316 }
317
318 // Debug
319 if (debug) {
320 std::cout << "Task " << FML::ThisTask << " will send " << n_send_right << " to " << RightTask
321 << "\n";
322 std::cout << "Task " << FML::ThisTask << " will send " << n_send_left << " to " << LeftTask
323 << "\n";
324 std::cout << "Task " << FML::ThisTask << " will recv " << n_recv_right << " from " << RightTask
325 << "\n";
326 std::cout << "Task " << FML::ThisTask << " will recv " << n_recv_left << " from " << LeftTask
327 << "\n";
328 std::cout << "Send fraction " << (n_send_left + n_send_right) / double(NumPart) << "\n";
329 }
330
331 // Compute how many bytes to send/recv
332 const size_t bytes_send_left = bytes_per_particle * n_send_left;
333 const size_t bytes_send_right = bytes_per_particle * n_send_right;
334 const size_t bytes_recv_left = bytes_per_particle * n_recv_left;
335 const size_t bytes_recv_right = bytes_per_particle * n_recv_right;
336
337 // Set up communication buffer
338 std::vector<char> CommBufferSend;
339 std::vector<char> CommBufferRecv;
340 CommBufferSend.resize(std::max(bytes_send_left, bytes_send_right));
341 CommBufferRecv.resize(std::max(bytes_recv_left, bytes_recv_right));
342
343 // Set up storage for boundary particles
344 boundary_particles.resize(n_recv_left + n_recv_right);
345
346 // Gather data to send left
347 char * buffer = CommBufferSend.data();
348 if (n_send_left > 0)
349 for (size_t i = 0; i < NumPart; i++) {
350 const auto * pos = FML::PARTICLE::GetPos(part[i]);
351 if (pos[0] < xmin + dx_boundary) {
352 FML::PARTICLE::AppendToBuffer(part[i], buffer);
353 buffer += bytes_per_particle;
354 }
355 }
356
357 // Send left recieve from right
358 MPI_Sendrecv(CommBufferSend.data(),
359 bytes_send_left,
360 MPI_BYTE,
361 LeftTask,
362 0,
363 CommBufferRecv.data(),
364 bytes_recv_right,
365 MPI_BYTE,
366 RightTask,
367 0,
368 MPI_COMM_WORLD,
369 &status);
370
371 // Generate particles
372 buffer = CommBufferRecv.data();
373 for (size_t i = 0; i < n_recv_right; i++) {
374 FML::PARTICLE::AssignFromBuffer(boundary_particles[i], buffer);
375 buffer += bytes_per_particle;
376 }
377
378 // Gather data to send right
379 buffer = CommBufferSend.data();
380 if (n_send_right > 0)
381 for (size_t i = 0; i < NumPart; i++) {
382 const auto * pos = FML::PARTICLE::GetPos(part[i]);
383 if (pos[0] > xmax - dx_boundary) {
384 FML::PARTICLE::AppendToBuffer(part[i], buffer);
385 buffer += bytes_per_particle;
386 }
387 }
388
389 // Send right recieve from left
390 MPI_Sendrecv(CommBufferSend.data(),
391 bytes_send_right,
392 MPI_BYTE,
393 RightTask,
394 0,
395 CommBufferRecv.data(),
396 bytes_recv_left,
397 MPI_BYTE,
398 LeftTask,
399 0,
400 MPI_COMM_WORLD,
401 &status);
402
403 // Generate particles
404 buffer = CommBufferRecv.data();
405 for (size_t i = 0; i < n_recv_left; i++) {
406 FML::PARTICLE::AssignFromBuffer(boundary_particles[n_recv_right + i], buffer);
407 buffer += bytes_per_particle;
408 }
409
410 // Free up memory used for communication
411 CommBufferSend = std::vector<char>();
412 CommBufferRecv = std::vector<char>();
413
414 // Make sure the left particles are wrapped to the left of the box on first task
415 // If we don't do this then we need to modify dx[0] comp in get_cell_index in ParticlesInGrid
416 // to wrap + the same in FindAllFriends
417 if (FML::ThisTask == 0) {
418 for (size_t i = 0; i < n_recv_left; i++) {
419 auto * pos = FML::PARTICLE::GetPos(boundary_particles[n_recv_right + i]);
420 pos[0] -= 1.0;
421 }
422 }
423
424 // Make sure the right particles are wrapped to the right of the box on last task
425 if (FML::ThisTask == FML::NTasks - 1) {
426 for (size_t i = 0; i < n_recv_right; i++) {
427 auto * pos = FML::PARTICLE::GetPos(boundary_particles[i]);
428 pos[0] += 1.0;
429 }
430 }
431 }
432 foftimer.EndTiming("Communicate boundary", FML::ThisTask == 0);
433#endif
434
435 const auto NumPart_boundary = boundary_particles.size();
436 const auto NumPart_total = NumPart + NumPart_boundary;
437
438 //=========================================================================
439 // Bin particles to a grid for faster linking
440 //=========================================================================
441 std::vector<FoFCell> PartCells;
442 int Local_nx{};
443 foftimer.StartTiming("ParticlesToCells");
444 ParticlesToCells<T, NDIM>(Ngrid,
445 Local_nx,
446 dx_boundary,
447 part,
448 NumPart,
449 boundary_particles.data(),
450 boundary_particles.size(),
451 PartCells);
452 foftimer.EndTiming("ParticlesToCells");
453
454 //=========================================================================
455 // We now have all the boundary particles and can do the FoF linking
456 //=========================================================================
457 if (FML::ThisTask == 0) {
458 std::cout << "\n";
459 std::cout << "#=====================================================\n";
460 std::cout << "#\n";
461 std::cout << "# ___________________ ___________ \n";
462 std::cout << "# \\_ _____/\\_____ \\ \\_ _____/ \n";
463 std::cout << "# | __) / | \\ | __) \n";
464 std::cout << "# | \\ / | \\| \\ \n";
465 std::cout << "# \\___ / \\_______ /\\___ / \n";
466 std::cout << "# \\/ \\/ \\/ \n";
467 std::cout << "#\n";
468 std::cout << "# FriendsOfFriends linking\n";
469 std::cout << "# FoF Linking Length: " << linking_length << "\n";
470 std::cout << "# FoF Linking Distance: " << fof_distance << "\n";
471 std::cout << "# dx_boundary / Boxsize: " << Buffersize_over_Boxsize << "\n";
472 std::cout << "# Periodic box: " << std::boolalpha << periodic << "\n";
473 std::cout << "# FoF linking Gridsize = " << Ngrid << " Local_nx: " << Local_nx << "\n";
474 std::cout << "# Maximum possible FoF gridsize = " << int(0.5 / fof_distance) << "\n";
475 std::cout << "# Npart_boundary : " << NumPart_boundary << " ( "
476 << NumPart_boundary / std::pow(1024.0, 2) * bytes_per_particle << " MB )\n";
477 std::cout << "# Grid memory per task: "
478 << (double(Local_nx) * FML::power(Ngrid, NDIM - 1) * sizeof(FoFCell) +
479 NumPart_total * sizeof(size_t)) /
480 std::pow(1024.0, 2)
481 << " MB\n";
482 std::cout << "# ...compared to particle memory: " << NumPart * bytes_per_particle / std::pow(1024.0, 2)
483 << " MB\n";
484 std::cout << "#\n";
485 std::cout << "#=====================================================\n";
486 std::cout << "\n";
487 }
488
489 //=========================================================================
490 // Link together all particles in the local domain that are located within
491 // a linkking distance of linking_length times the mean particle separation.
492 // The result is a list of all the particles and an associated FoF group ID
493 // This ID can be arbitrary but is guaranteed to be unique across tasks
494 //=========================================================================
495
496 // Allocate the FoF id for each particle
497 auto particle_id_FoF = std::vector<size_t>(NumPart + NumPart_boundary, no_FoF_ID);
498
499 // Picks out the particle
500 auto type_from_globalindex = [&](size_t globalindex) {
501 if (globalindex < NumPart_boundary)
502 return int(BOUNDARY_PARTICLE);
503 return int(REGULAR_PARTICLE);
504 };
505 auto localindex_from_globalindex = [&](size_t globalindex) {
506 if (globalindex < NumPart_boundary)
507 return globalindex;
508 return globalindex - NumPart_boundary;
509 };
510 auto globalindex_from_localindex_type = [&](size_t localindex, int type) {
511 if (type == BOUNDARY_PARTICLE)
512 return localindex;
513 return localindex + NumPart_boundary;
514 };
515 auto particle_from_localindex_and_type = [&](size_t localindex, int type) {
516 if (type == BOUNDARY_PARTICLE)
517 return &boundary_particles[localindex];
518 return &part[localindex];
519 };
520
521 // Reserve space for 1000 particles in a FoF group
522 std::vector<size_t> friend_local_index_list;
523 std::vector<signed char> friend_type_list;
524 friend_local_index_list.reserve(1000);
525 friend_type_list.reserve(1000);
526
527 // Function to locate and tag all closest friends
528 // This is then used recursively in the next method we have
529 auto FindAllFriends = [&](T * curpart,
530 [[maybe_unused]] int type,
531 [[maybe_unused]] size_t localindex,
532 size_t globalindex,
533 size_t FoFID,
534 std::vector<size_t> & friend_local_index_list,
535 std::vector<signed char> & friend_type_list) {
536 // Get the particle
537 const auto * pos1 = FML::PARTICLE::GetPos(*curpart);
538
539 // Compute coord of cell particle is in
540 std::array<int, NDIM> coord;
541 std::array<int, NDIM> di;
542 for (int idim = 0; idim < NDIM; idim++) {
543 double ix = (pos1[idim] - (xmin - dx_boundary) * (idim == 0 ? 1 : 0)) * Ngrid;
544 coord[idim] = int(ix);
545 di[idim] = (ix - coord[idim] > 0.5) ? +1 : -1;
546 }
547
548 // Loop through all 2^NDIM nbor cells
549 // We only go to the cells on the same side as the particle position
550 // As long as the cell-size is larger than 2*fof_distance we don't need
551 // to visit the other cells
552 std::array<int, NDIM> icoord;
553 for (int nbcell = 0; nbcell < twotondim; nbcell++) {
554 for (int idim = 0, n = 1; idim < NDIM; idim++, n *= 2) {
555 int go_left_right_or_stay = (nbcell / n % 2);
556 icoord[idim] = coord[idim] + go_left_right_or_stay * di[idim];
557 }
558
559 // For boundary cells in the x direction we don't have a left or a right nbor
560 // unless we only have 1 task
561 if (FML::NTasks > 1) {
562 if (icoord[0] < 0 or icoord[0] >= Local_nx)
563 continue;
564 } else {
565 if (icoord[0] < 0)
566 icoord[0] += Ngrid;
567 if (icoord[0] >= Ngrid)
568 icoord[0] -= Ngrid;
569 }
570
571 // Compute cell-index or nbor cell
572 bool skip = false;
573 size_t index_nbor_cell = icoord[0];
574 for (int idim = 1; idim < NDIM; idim++) {
575 // Periodic boundary conditions
576 if (periodic) {
577 if (icoord[idim] < 0)
578 icoord[idim] += Ngrid;
579 if (icoord[idim] >= Ngrid)
580 icoord[idim] -= Ngrid;
581 } else {
582 if (icoord[idim] < 0 or icoord[idim] >= Ngrid){
583 skip = true;
584 break;
585 }
586 }
587 index_nbor_cell = index_nbor_cell * Ngrid + icoord[idim];
588 }
589
590 // Cell do not exist in this case
591 if (skip)
592 continue;
593
594 // Get total number of particles in cell
595 const size_t np_boundary = PartCells[index_nbor_cell].np_boundary;
596 const size_t np = PartCells[index_nbor_cell].np;
597 const size_t np_total = np + np_boundary;
598
599 // Loop over all particles in nbor cell
600 for (size_t ii = 0; ii < np_total; ii++) {
601 T * nborpart{};
602 int nbortype{};
603 size_t nborglobalindex{};
604 size_t nborlocalindex{};
605
606 // Type of particle
607 if (ii < np_boundary) {
608 nborlocalindex = PartCells[index_nbor_cell].ParticleIndexBoundary[ii];
609 nborglobalindex = nborlocalindex;
610 nborpart = &boundary_particles[nborlocalindex];
611 nbortype = BOUNDARY_PARTICLE;
612 } else {
613 nborlocalindex = PartCells[index_nbor_cell].ParticleIndex[ii - np_boundary];
614 nborglobalindex = nborlocalindex + NumPart_boundary;
615 nborpart = &part[nborlocalindex];
616 nbortype = REGULAR_PARTICLE;
617 }
618
619 // If same particle or already processed so that it has an ID skip going further
620 if (particle_id_FoF[nborglobalindex] != no_FoF_ID)
621 continue;
622 if (nborglobalindex == globalindex)
623 continue;
624
625 // Compute distance
626 double dist2 = 0.0;
627 const auto * pos2 = FML::PARTICLE::GetPos(*nborpart);
628 std::array<double, NDIM> dx2;
629 for (int idim = 0; idim < NDIM; idim++) {
630 dx2[idim] = std::abs(pos2[idim] - pos1[idim]);
631 if (dx2[idim] > 0.5 and periodic)
632 dx2[idim] -= 1.0;
633 }
634 for (int idim = 0; idim < NDIM; idim++)
635 dist2 += dx2[idim] * dx2[idim];
636
637 // Check if we have a link. Set the FoF ID in the particle list
638 if (dist2 < fof_distance2) {
639 particle_id_FoF[nborglobalindex] = FoFID;
640 // Only need to set this one time so avoid a write by adding if test
641 if (particle_id_FoF[globalindex] == no_FoF_ID)
642 particle_id_FoF[globalindex] = FoFID;
643 // Push partice info to FoF list
644 friend_local_index_list.push_back(nborlocalindex);
645 friend_type_list.push_back(nbortype);
646 }
647 }
648 }
649 };
650
651 // ID of the FoF groups
652 unsigned int FoFID = 0;
653
654 // Recursively find all friends of friends
655 foftimer.StartTiming("FoFLinking");
656 for (size_t i = 0; i < NumPart_total; i++) {
657
658 // Show progress bar
659 if (FML::ThisTask == 0)
660 if ((10 * i) / NumPart_total != (10 * i + 10) / NumPart_total or i == 0) {
661 std::cout << (100 * i + 100) / NumPart_total << "% " << std::flush;
662 if (i == NumPart_total - 1) {
663 std::cout << std::endl;
664 }
665 }
666
667 // Fetch the particle
668 size_t globalindex = i;
669 size_t localindex = localindex_from_globalindex(globalindex);
670 int type = type_from_globalindex(globalindex);
671 T * curpart = particle_from_localindex_and_type(localindex, type);
672
673 // Check if particle is not already processed
674 if (particle_id_FoF[i] == no_FoF_ID) {
675 // Start new FoF group
676 friend_local_index_list.clear();
677 friend_type_list.clear();
678
679 // Do linking
680 FindAllFriends(
681 curpart, type, localindex, globalindex, FoFID, friend_local_index_list, friend_type_list);
682
683 if (friend_local_index_list.size() == 0)
684 continue;
685
686 // We are here if we found a group (2 or more particles)
687 // Go through all friends and the friend of these friends
688 // until we have found all particles in the halo
689 FoFHalo<T, NDIM> newhalo(FoFID);
690 newhalo.add(*curpart, periodic);
691
692 while (friend_local_index_list.size() > 0) {
693 // Fetch a particle
694 auto localindex = friend_local_index_list.back();
695 type = int(friend_type_list.back());
696 globalindex = globalindex_from_localindex_type(localindex, type);
697 curpart = particle_from_localindex_and_type(localindex, type);
698
699 // Fetch position relative to first particle
700 newhalo.add(*curpart, periodic);
701
702 // Remove particle from list
703 friend_local_index_list.pop_back();
704 friend_type_list.pop_back();
705
706 // Do linking
707 FindAllFriends(
708 curpart, type, localindex, globalindex, FoFID, friend_local_index_list, friend_type_list);
709 }
710
711 // Save halo if it lies in the local domain and has more than nmin particles
712 if (int(newhalo.np) >= nmin_FoF_group) {
713 double xhalo = newhalo.get_pos()[0];
714 if (periodic) // Periodic wrap
715 xhalo += (xhalo < 0.0) ? 1.0 : ((xhalo >= 1.0) ? -1.0 : 0.0);
716 if (xhalo >= FML::xmin_domain and xhalo < FML::xmax_domain) {
717 LocalFoFGroups.push_back(newhalo);
718 }
719 }
720
721 // Increase the FoF ID
722 FoFID++;
723 }
724 }
725#ifdef USE_MPI
726 MPI_Barrier(MPI_COMM_WORLD);
727#endif
728 foftimer.EndTiming("FoFLinking");
729
730 // Grid no longer needed, free up memory
731 delete[] particlegriddata;
732
733 // Show timings
734 if (FML::ThisTask == 0)
736
737 // Print how many particles we found
738 auto nhalos = LocalFoFGroups.size();
739 FML::SumOverTasks(&nhalos);
740 if (FML::ThisTask == 0)
741 std::cout << "# We found a total of " << nhalos << " (" << LocalFoFGroups.size()
742 << " on task 0) "
743 "\n";
744
745 // We need to fix the FoFID of the halos as they start from 0 on each task
746 // so that all just have a unique ID
747 // We do this such that after mpi-sum we have the sum of the FoFIDs
748 std::vector<int> addfofid(FML::NTasks, 0);
749 for (int i = FML::ThisTask + 1; i < FML::NTasks; i++)
750 addfofid[i] = FoFID;
751#ifdef USE_MPI
752 MPI_Allreduce(MPI_IN_PLACE, addfofid.data(), NTasks, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
753#endif
754
755 // Finalize computation of halos
756 auto add = addfofid[FML::ThisTask];
757 for (auto & h : LocalFoFGroups) {
758 h.id += add;
759 h.finalize(periodic);
760 }
761
762 // If particles have a set_fofid method then set the ID in the particles
763 // This sets it to no_FoF_id if the particle is not part of a group
764 if constexpr (FML::PARTICLE::has_set_fofid<T>()) {
765 for (size_t i = 0; i < NumPart_total; i++) {
766 auto FoFID = particle_id_FoF[i] + addfofid[FML::ThisTask];
767 if (type_from_globalindex(i) == BOUNDARY_PARTICLE)
768 FML::PARTICLE::SetFoFID(boundary_particles[localindex_from_globalindex(i)], FoFID);
769 else if (type_from_globalindex(i) == REGULAR_PARTICLE)
770 FML::PARTICLE::SetFoFID(part[localindex_from_globalindex(i)], FoFID);
771 }
772 // TODO - id should be communicated back for the boundary particles
773 }
774
775 // Boundary particles are finally freed here
776 }
const int NDIM
const double h
Definition: Main.cpp:66
const double linking_length
Definition: Main.cpp:59
This class defines how to compute the halos from the particles.
Definition: FoFBinning.h:28
void StartTiming(const char *name)
Start timing.
Definition: Timings.h:63
void PrintAllTimings() const
Output all the recorded timings.
Definition: Timings.h:41
double EndTiming(const char *name, bool print=false)
End timing.
Definition: Timings.h:83
const constexpr size_t no_FoF_ID
If a particle belongs to no FoF group it is given this FoF ID.
Definition: FoF.h:23
Timings foftimer
Records timings of the FoF search.
Definition: FoF.h:35
int FoF_Ngrid_max
The maximum grid we are allowed to use to bin the particle to This is just to have a memory limit in ...
Definition: FoF.h:31
size_t * particlegriddata
Definition: FoF.h:20
void AssignFromBuffer(T &t, Args... args)
void AppendToBuffer(T &t, Args... args)
get_vel constexpr double * GetPos(...)
constexpr void SetFoFID(...)
void axis(const std::string &axisstr)
double xmax_domain
The local extent of the domain (global domain goes from 0 to 1)
Definition: Global.cpp:42
constexpr long long int power(int base, int exponent)
Simple integer a^b power-function by squaring.
Definition: Global.h:187
int ThisTask
The MPI task number.
Definition: Global.cpp:33
void SumOverTasks(T *value)
Inplace sum over tasks of a single value.
Definition: Global.h:147
double xmin_domain
The local extent of the domain (global domain goes from 0 to 1)
Definition: Global.cpp:41
int NTasks
Total number of MPI tasks.
Definition: Global.cpp:34

◆ ParticlesToCells()

template<class T , int NDIM>
void FML::FOF::ParticlesToCells ( int  Ngrid,
int &  Local_nx,
double  dx_boundary,
T *  part,
size_t  NumPart,
T *  part_boundary,
size_t  NumPart_boundary,
std::vector< FoFCell > &  PartCells 
)

Internal method: bin particles to a grid.

NB: this method assumes the boundary particles are unwrapped when we run with a periodic box. i.e. on task 1 they should have x<0 (-0.1, not 0.9 if inside the box) and on task N-1 they should have x>1 (1.1, not 0.1 if inside the box)

Definition at line 94 of file FoF.h.

101 {
102
103 // Total length of domain
104 const double dx_cells = 1.0 / Ngrid;
105 const double xmin_box = FML::xmin_domain - dx_boundary;
106 const double xmax_box = FML::xmax_domain + dx_boundary;
107 const double dx_box_domain = xmax_box - xmin_box;
108
109 // Total number of local-cells
110 Local_nx = std::ceil(dx_box_domain / dx_cells);
111
112 // Total number of local cells to allocate
113 const size_t NgridTot = size_t(Local_nx) * FML::power(Ngrid, NDIM - 1);
114
115 // Make a grid containing particles
116 PartCells.resize(NgridTot);
117
118 // Get index of the cell the particle lives in
119 auto get_cell_index = [&](size_t ipart, std::vector<int> & coord, int type) {
120 decltype(part[0].get_pos()) pos;
121 if (type == BOUNDARY_PARTICLE)
122 pos = FML::PARTICLE::GetPos(part_boundary[ipart]);
123 else if (type == REGULAR_PARTICLE)
124 pos = FML::PARTICLE::GetPos(part[ipart]);
125 else {
126 throw std::runtime_error("FoF::The particle is somehow marked as not being left/regular/right! "
127 "Should not happen ever!\n");
128 }
129
130 size_t index_cell = 0;
131 for (int idim = 0; idim < NDIM; idim++) {
132 auto dx = (pos[idim] - xmin_box * (idim == 0 ? 1 : 0));
133 coord[idim] = int(dx * Ngrid);
134 index_cell = index_cell * Ngrid + coord[idim];
135 }
136 return index_cell;
137 };
138
139 // Count number of particles in cells
140 std::vector<int> coord(NDIM);
141 for (size_t i = 0; i < NumPart; i++) {
142 const size_t index_cell = get_cell_index(i, coord, REGULAR_PARTICLE);
143 PartCells[index_cell].np++;
144 }
145 for (size_t i = 0; i < NumPart_boundary; i++) {
146 const size_t index_cell = get_cell_index(i, coord, BOUNDARY_PARTICLE);
147 PartCells[index_cell].np_boundary++;
148 }
149
150 // Allocate all the data
151 particlegriddata = new size_t[NumPart_boundary + NumPart];
152 size_t * ptr = &particlegriddata[0];
153
154 // Allocate cells
155 int np_max = 0;
156 size_t np_total = 0;
157 size_t np_boundary_total = 0;
158 for (auto & cell : PartCells) {
159 cell.ParticleIndexBoundary = ptr;
160 ptr += cell.np_boundary;
161 cell.ParticleIndex = ptr;
162 ptr += cell.np;
163
164 np_max = std::max(np_max, cell.np);
165 np_total += cell.np;
166 np_boundary_total += cell.np_boundary;
167 cell.np = 0;
168 cell.np_boundary = 0;
169 }
170
171 // Fill up cells with info about particles
172 for (size_t i = 0; i < NumPart; i++) {
173 const size_t index_cell = get_cell_index(i, coord, 0);
174 auto & np = PartCells[index_cell].np;
175 PartCells[index_cell].ParticleIndex[np] = i;
176 np++;
177 }
178 for (size_t i = 0; i < NumPart_boundary; i++) {
179 const size_t index_cell = get_cell_index(i, coord, -1);
180 auto & np_boundary = PartCells[index_cell].np_boundary;
181 PartCells[index_cell].ParticleIndexBoundary[np_boundary] = i;
182 np_boundary++;
183 }
184 }

Variable Documentation

◆ FoF_Ngrid_max

int FML::FOF::FoF_Ngrid_max = 1024

The maximum grid we are allowed to use to bin the particle to This is just to have a memory limit in what the algorithm is allowed to allocate.

This value is used if user do not provide it.

Definition at line 31 of file FoF.h.

◆ foftimer

Timings FML::FOF::foftimer

Records timings of the FoF search.

Definition at line 35 of file FoF.h.

◆ no_FoF_ID

const constexpr size_t FML::FOF::no_FoF_ID = std::numeric_limits<size_t>::max()
constexpr

If a particle belongs to no FoF group it is given this FoF ID.

Definition at line 23 of file FoF.h.

◆ particlegriddata

size_t* FML::FOF::particlegriddata {nullptr}

Definition at line 20 of file FoF.h.