Locate friends of friends groups and bin what you want over these groups.
194 {
195
196 LocalFoFGroups.clear();
197 if (NumPart == 0)
198 return;
199 const bool debug = false;
200
201
202 assert_mpi(FML::PARTICLE::GetNDIM(T()) ==
NDIM,
203 "FriendsOfFriends::The dimensions of particle and method do not match!\n");
204
205
206 size_t NumPart_tot = NumPart;
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
214 Ngrid = std::min(int(0.5 / fof_distance), Ngrid);
215
216
218
219
220 assert_mpi(1.0 / Ngrid > 2 * fof_distance,
221 "FriendsOfFriends::The gridsize is too large (larger than the 2x linking distance)");
222
223
224
226 std::sort(part, part + NumPart, [](const T & a, const T & b) {
227
228 constexpr int axis = 1;
229 T * f = const_cast<T *>(&a);
230 T * g = const_cast<T *>(&b);
234 });
236
237
240 const double dx_boundary =
FML::NTasks == 1 ? 0.0 : Buffersize_over_Boxsize;
241
242
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
250
251
252 std::vector<T> boundary_particles{};
253 const size_t bytes_per_particle = FML::PARTICLE::GetSize(part[0]);
254#ifdef USE_MPI
257
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++) {
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
275 MPI_Status status;
278
279
280 size_t n_recv_left = 0;
281 size_t n_recv_right = 0;
282
283
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
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
311 if (not periodic) {
313 n_recv_left = n_send_left = 0;
315 n_recv_right = n_send_right = 0;
316 }
317
318
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
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
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
344 boundary_particles.resize(n_recv_left + n_recv_right);
345
346
347 char * buffer = CommBufferSend.data();
348 if (n_send_left > 0)
349 for (size_t i = 0; i < NumPart; i++) {
351 if (pos[0] < xmin + dx_boundary) {
353 buffer += bytes_per_particle;
354 }
355 }
356
357
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
372 buffer = CommBufferRecv.data();
373 for (size_t i = 0; i < n_recv_right; i++) {
375 buffer += bytes_per_particle;
376 }
377
378
379 buffer = CommBufferSend.data();
380 if (n_send_right > 0)
381 for (size_t i = 0; i < NumPart; i++) {
383 if (pos[0] > xmax - dx_boundary) {
385 buffer += bytes_per_particle;
386 }
387 }
388
389
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
404 buffer = CommBufferRecv.data();
405 for (size_t i = 0; i < n_recv_left; i++) {
407 buffer += bytes_per_particle;
408 }
409
410
411 CommBufferSend = std::vector<char>();
412 CommBufferRecv = std::vector<char>();
413
414
415
416
418 for (size_t i = 0; i < n_recv_left; i++) {
420 pos[0] -= 1.0;
421 }
422 }
423
424
426 for (size_t i = 0; i < n_recv_right; i++) {
428 pos[0] += 1.0;
429 }
430 }
431 }
433#endif
434
435 const auto NumPart_boundary = boundary_particles.size();
436 const auto NumPart_total = NumPart + NumPart_boundary;
437
438
439
440
441 std::vector<FoFCell> PartCells;
442 int Local_nx{};
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);
453
454
455
456
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";
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
491
492
493
494
495
496
497 auto particle_id_FoF = std::vector<size_t>(NumPart + NumPart_boundary,
no_FoF_ID);
498
499
500 auto type_from_globalindex = [&](size_t globalindex) {
501 if (globalindex < NumPart_boundary)
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) {
512 return localindex;
513 return localindex + NumPart_boundary;
514 };
515 auto particle_from_localindex_and_type = [&](size_t localindex, int type) {
517 return &boundary_particles[localindex];
518 return &part[localindex];
519 };
520
521
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
528
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
538
539
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
549
550
551
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
560
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
572 bool skip = false;
573 size_t index_nbor_cell = icoord[0];
574 for (
int idim = 1; idim <
NDIM; idim++) {
575
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
591 if (skip)
592 continue;
593
594
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
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
607 if (ii < np_boundary) {
608 nborlocalindex = PartCells[index_nbor_cell].ParticleIndexBoundary[ii];
609 nborglobalindex = nborlocalindex;
610 nborpart = &boundary_particles[nborlocalindex];
612 } else {
613 nborlocalindex = PartCells[index_nbor_cell].ParticleIndex[ii - np_boundary];
614 nborglobalindex = nborlocalindex + NumPart_boundary;
615 nborpart = &part[nborlocalindex];
617 }
618
619
620 if (particle_id_FoF[nborglobalindex] !=
no_FoF_ID)
621 continue;
622 if (nborglobalindex == globalindex)
623 continue;
624
625
626 double dist2 = 0.0;
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
638 if (dist2 < fof_distance2) {
639 particle_id_FoF[nborglobalindex] = FoFID;
640
641 if (particle_id_FoF[globalindex] ==
no_FoF_ID)
642 particle_id_FoF[globalindex] = FoFID;
643
644 friend_local_index_list.push_back(nborlocalindex);
645 friend_type_list.push_back(nbortype);
646 }
647 }
648 }
649 };
650
651
652 unsigned int FoFID = 0;
653
654
656 for (size_t i = 0; i < NumPart_total; i++) {
657
658
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
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
675
676 friend_local_index_list.clear();
677 friend_type_list.clear();
678
679
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
687
688
690 newhalo.add(*curpart, periodic);
691
692 while (friend_local_index_list.size() > 0) {
693
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
700 newhalo.add(*curpart, periodic);
701
702
703 friend_local_index_list.pop_back();
704 friend_type_list.pop_back();
705
706
707 FindAllFriends(
708 curpart, type, localindex, globalindex, FoFID, friend_local_index_list, friend_type_list);
709 }
710
711
712 if (int(newhalo.np) >= nmin_FoF_group) {
713 double xhalo = newhalo.get_pos()[0];
714 if (periodic)
715 xhalo += (xhalo < 0.0) ? 1.0 : ((xhalo >= 1.0) ? -1.0 : 0.0);
717 LocalFoFGroups.push_back(newhalo);
718 }
719 }
720
721
722 FoFID++;
723 }
724 }
725#ifdef USE_MPI
726 MPI_Barrier(MPI_COMM_WORLD);
727#endif
729
730
732
733
736
737
738 auto nhalos = LocalFoFGroups.size();
741 std::cout << "# We found a total of " << nhalos << " (" << LocalFoFGroups.size()
742 << " on task 0) "
743 "\n";
744
745
746
747
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
757 for (
auto &
h : LocalFoFGroups) {
759 h.finalize(periodic);
760 }
761
762
763
764 if constexpr (FML::PARTICLE::has_set_fofid<T>()) {
765 for (size_t i = 0; i < NumPart_total; i++) {
771 }
772
773 }
774
775
776 }
const double linking_length
This class defines how to compute the halos from the particles.
void StartTiming(const char *name)
Start timing.
void PrintAllTimings() const
Output all the recorded timings.
double EndTiming(const char *name, bool print=false)
End timing.
const constexpr size_t no_FoF_ID
If a particle belongs to no FoF group it is given this FoF ID.
Timings foftimer
Records timings of the FoF search.
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 ...
size_t * particlegriddata
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)
constexpr long long int power(int base, int exponent)
Simple integer a^b power-function by squaring.
int ThisTask
The MPI task number.
void SumOverTasks(T *value)
Inplace sum over tasks of a single value.
double xmin_domain
The local extent of the domain (global domain goes from 0 to 1)
int NTasks
Total number of MPI tasks.