/******************************************************************************* * This file is part of SWIFT. * Copyright (c) 2024 Will Roper (w.roper@sussex.ac.uk) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * ******************************************************************************/ /* Config parameters. */ #include /* Local headers. */ #include "active.h" #include "cell.h" #include "engine.h" #include "gravity_properties.h" #include "runner.h" #include "runner_doiact_grav.h" #include "space.h" #include "timers.h" /** * @brief Performs M-M interactions between a given top-level cell and * all other top level cells not interacted with via pair tasks. * * This is the non-periodic case where there is no mesh so all cells not * handled by a pair task are interacted with here in this long range * gravity function. * * @param r The thread #runner. * @param ci The #cell of interest. * @param top The top-level parent of the #cell of interest. */ void runner_do_grav_long_range_non_periodic(struct runner *r, struct cell *ci, struct cell *top) { struct engine *e = r->e; /* Get the multipole of the cell we are interacting. */ struct gravity_tensors *const multi_i = ci->grav.multipole; /* Recover the list of top-level cells */ struct cell *cells = e->s->cells_top; int *cells_with_particles = e->s->cells_with_particles_top; const int nr_cells_with_particles = e->s->nr_cells_with_particles; /* Loop over all the top-level cells and go for a M-M interaction if * well-separated */ for (int n = 0; n < nr_cells_with_particles; ++n) { /* Handle on the top-level cell and it's gravity business*/ struct cell *cj = &cells[cells_with_particles[n]]; struct gravity_tensors *const multi_j = cj->grav.multipole; /* Avoid self contributions */ if (top == cj) continue; /* Skip empty cells */ if (multi_j->m_pole.M_000 == 0.f) continue; if (cell_can_use_pair_mm(top, cj, e, e->s, /*use_rebuild_data=*/1, /*is_tree_walk=*/0)) { /* Call the PM interaction function on the active sub-cells of ci */ runner_dopair_grav_mm_nonsym(r, ci, cj); // runner_dopair_recursive_grav_pm(r, ci, cj); /* Record that this multipole received a contribution */ multi_i->pot.interacted = 1; } /* We are in charge of this pair */ } /* Loop over top-level cells */ } /** * @brief Performs M-M interactions between a given top-level cell and all other * top level cells not interacted with via pair tasks or the mesh. * * This is used when the space is periodic and there is a mesh, therefore we * only interact with cells that are closer than the mesh interaction distance * but further than the direct interaction distance. * * This function will be "clever" and only loop over the parts of the * top-level grid that are not covered by the mesh. Add some buffer for * safety. * * @param r The thread #runner. * @param ci The #cell of interest. * @param top The top-level parent of the #cell of interest. */ void runner_do_grav_long_range_periodic(struct runner *r, struct cell *ci, struct cell *top) { struct engine *e = r->e; struct space *s = e->s; struct cell *cells = s->cells_top; const int periodic = e->mesh->periodic; const double dim[3] = {e->mesh->dim[0], e->mesh->dim[1], e->mesh->dim[2]}; /* Get the maximum distance at which we can have a non-mesh interaction. */ const double max_distance = e->mesh->r_cut_max; const double max_distance2 = max_distance * max_distance; /* Get the multipole of the cell we are interacting. */ struct gravity_tensors *const multi_i = ci->grav.multipole; /* Get the (i,j,k) location of the top-level cell in the grid. */ int top_i = top->loc[0] * s->iwidth[0]; int top_j = top->loc[1] * s->iwidth[1]; int top_k = top->loc[2] * s->iwidth[2]; /* Maximal distance any interaction can take place before the mesh kicks in, * rounded up to the next integer */ int d = ceil(max_distance * max3(s->iwidth[0], s->iwidth[1], s->iwidth[2])) + 1; /* Ensure we don't go out of bounds */ if (d > s->cdim[0] / 2) d = s->cdim[0] / 2; if (d > s->cdim[1] / 2) d = s->cdim[1] / 2; if (d > s->cdim[2] / 2) d = s->cdim[2] / 2; /* Loop over plausibly useful cells */ for (int ii = top_i - d; ii <= top_i + d; ++ii) { for (int jj = top_j - d; jj <= top_j + d; ++jj) { for (int kk = top_k - d; kk <= top_k + d; ++kk) { /* Box wrap */ const int iii = (ii + s->cdim[0]) % s->cdim[0]; const int jjj = (jj + s->cdim[1]) % s->cdim[1]; const int kkk = (kk + s->cdim[2]) % s->cdim[2]; /* Get the cell */ const int cell_index = cell_getid(s->cdim, iii, jjj, kkk); /* Handle on the top-level cell */ struct cell *cj = &cells[cell_index]; /* Avoid self contributions */ if (top == cj) continue; /* Handle on the top-level cell's gravity business*/ const struct gravity_tensors *multi_j = cj->grav.multipole; /* Skip empty cells */ if (multi_j->m_pole.M_000 == 0.f) continue; /* Minimal distance between any pair of particles */ const double min_radius2 = cell_min_dist2_same_size(top, cj, periodic, dim); /* Are we beyond the distance where the truncated forces are 0 ?*/ if (min_radius2 > max_distance2) { /* Record that this multipole received a contribution */ multi_i->pot.interacted = 1; /* We are done here. */ continue; } /* Shall we interact with this cell? */ if (cell_can_use_pair_mm(top, cj, e, e->s, /*use_rebuild_data=*/1, /*is_tree_walk=*/0)) { /* Call the PM interaction function on the active sub-cells of ci */ runner_dopair_grav_mm_nonsym(r, ci, cj); // runner_dopair_recursive_grav_pm(r, ci, cj); /* Record that this multipole received a contribution */ multi_i->pot.interacted = 1; } /* We can interact with this cell */ } /* Loop over relevant top-level cells (k) */ } /* Loop over relevant top-level cells (j) */ } /* Loop over relevant top-level cells (i) */ } #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_GRAVITY_FORCE_CHECKS) /** * @brief Increment the mesh interaction counters. * * This is a helper function for incrementing the mesh interaction counters * for debugging purposes. * * @param multi_i The multipole receiving the interaction. * @param multi_j The multipole giving the interaction. */ static void runner_accumulate_interaction( struct gravity_tensors *restrict multi_i, struct gravity_tensors *restrict multi_j) { /* Ensure we aren't self-interacting */ if (multi_i == multi_j) { error("Self interactions should not be handled in this function!"); } #ifdef SWIFT_DEBUG_CHECKS /* Need to account for the mesh interactions we missed */ accumulate_add_ll(&multi_i->pot.num_interacted, multi_j->m_pole.num_gpart); #endif #ifdef SWIFT_GRAVITY_FORCE_CHECKS /* Need to account for the mesh interactions we missed */ accumulate_add_ll(&multi_i->pot.num_interacted_pm, multi_j->m_pole.num_gpart); #endif /* Record that this multipole received a contribution */ multi_i->pot.interacted = 1; } /** * @brief Count a mesh interaction between two related cells. * * Since the counts are accumulated downwards from the super level in the * grav/down task we need to update different cells based on the cells we have * been passed. This function will select what cell should be updated based on * the nesting: * - If the super cell is nested within ci, then the super cell is updated. * - If the super cell is ci, then they are both the same and it does not * matter which is updated. * - If ci is nested within the super cell, then ci is updated. * - If we have a self interaction (pair nested within the same top cell): * - If the super cell is nested within cj, then the super cell is updated. * - If the super cell is cj, then they are both the same and it does not * matter which is updated. * - If cj is nested within the super cell, then cj is updated. * * @param super The super-cell being updated. * @param ci The first #cell in the pair. * @param cj The second #cell in the pair. */ static void runner_count_mesh_interaction(struct cell *super, struct cell *ci, struct cell *cj) { /* Do we share the same top level cell? i.e. are we self-interacting? */ int is_self = ci->top == cj->top; /* Decide which cell we are updating. */ if (super == ci) { runner_accumulate_interaction(super->grav.multipole, cj->grav.multipole); } else if (cell_contains_progeny(ci, super)) { runner_accumulate_interaction(super->grav.multipole, cj->grav.multipole); } else if (cell_contains_progeny(super, ci)) { runner_accumulate_interaction(ci->grav.multipole, cj->grav.multipole); } /* Handle the symmetric case for self interactions */ if (is_self) { if (super == cj) { runner_accumulate_interaction(super->grav.multipole, ci->grav.multipole); } else if (cell_contains_progeny(cj, super)) { runner_accumulate_interaction(super->grav.multipole, ci->grav.multipole); } else if (cell_contains_progeny(super, cj)) { runner_accumulate_interaction(cj->grav.multipole, ci->grav.multipole); } } } /** * @brief Recursively accumulate mesh interactions for pair interactions. * * This function mirrors the logic in scheduler_splittask_gravity for pair * tasks, recursing down the cell hierarchy and counting mesh interactions. * * @param ci The #cell of interest (active cell receiving interactions). * @param cpi The current #cell from ci's hierarchy being processed. * @param cpj The current #cell from cj's hierarchy being processed. * @param s The #space. */ static void runner_count_mesh_interactions_pair_recursive(struct cell *c, struct cell *ci, struct cell *cj, struct space *s) { /* No self interactions here */ if (ci == cj) { error("Self interactions should not be handled in this function!"); } if (c == cj) { error("Self interactions should not be handled in this function!"); } struct engine *e = s->e; /* Should this pair be split? */ if (cell_can_split_pair_gravity_task(ci) && cell_can_split_pair_gravity_task(cj)) { /* Check particle count threshold - mirrors scheduler_splittask_gravity */ const long long gcount_i = ci->grav.count; const long long gcount_j = cj->grav.count; if (gcount_i * gcount_j < ((long long)space_subsize_pair_grav)) { return; } /* Recurse on all progeny pairs */ for (int i = 0; i < 8; i++) { if (ci->progeny[i] == NULL) continue; struct cell *cpi = ci->progeny[i]; for (int j = 0; j < 8; j++) { if (cj->progeny[j] == NULL) continue; struct cell *cpj = cj->progeny[j]; /* Can we use the mesh for this pair? */ if (cell_can_use_mesh(e, cpi, cpj)) { /* Record the mesh interaction */ runner_count_mesh_interaction(c, cpi, cpj); continue; } /* Can we use M-M for this pair? */ if (cell_can_use_pair_mm(cpi, cpj, e, s, /*use_rebuild_data=*/1, /*is_tree_walk=*/1)) { /* This would be handled by a M-M task, nothing to count */ continue; } /* We would create real tasks, so recurse to find mesh interactions */ runner_count_mesh_interactions_pair_recursive(c, cpi, cpj, s); } } } /* else: We have a real task that doesn't split further, no mesh * interactions to count */ } /** * @brief Recursively accumulate mesh interactions for self interactions. * * This function mirrors the logic in scheduler_splittask_gravity for self * tasks, recursing down the cell hierarchy and counting mesh interactions. * * @param c The #cell of interest (active cell receiving interactions). * @param ci The current #cell from c's hierarchy being processed. * @param s The #space. */ static void runner_count_mesh_interactions_self_recursive(struct cell *c, struct cell *ci, struct space *s) { struct engine *e = s->e; /* Should this self task be split? */ if (cell_can_split_self_gravity_task(ci)) { /* Check particle count threshold - mirrors scheduler_splittask_gravity */ if (ci->grav.count < space_subsize_self_grav) { return; } /* Recurse on self interactions for each progeny */ for (int k = 0; k < 8; k++) { if (ci->progeny[k] == NULL) continue; runner_count_mesh_interactions_self_recursive(c, ci->progeny[k], s); } /* Now handle pair interactions between progeny */ for (int j = 0; j < 8; j++) { if (ci->progeny[j] == NULL) continue; struct cell *cpj = ci->progeny[j]; for (int k = j + 1; k < 8; k++) { if (ci->progeny[k] == NULL) continue; struct cell *cpk = ci->progeny[k]; /* Can we use the mesh for this pair? */ if (cell_can_use_mesh(e, cpj, cpk)) { /* Record the mesh interaction */ runner_count_mesh_interaction(c, cpj, cpk); continue; } /* Otherwise recurse as a pair interaction */ runner_count_mesh_interactions_pair_recursive(c, cpj, cpk, s); } } } /* else: We have a real task that doesn't split further, no mesh * interactions to count */ } #endif /** * @brief Accumulate the number of particle mesh interactions for debugging * purposes. * * This function mirrors the task creation and splitting logic to count * mesh interactions that ci would receive from all top-level cells. * * NOTE: This will recurse over cells that are not directly realted to c (the * super cell of ci). It will not add their contribution though so is "safe". * * @param r The thread #runner. * @param ci The #cell of interest (active cell receiving interactions). * @param top The current top-level cell (ci's top-level parent). */ void runner_count_mesh_interactions(struct runner *r, struct cell *ci, struct cell *top) { #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_GRAVITY_FORCE_CHECKS) struct engine *e = r->e; struct space *s = e->s; struct cell *cells = s->cells_top; /* First, handle self interactions from the top-level cell. * This mirrors the self task created at the top level. */ runner_count_mesh_interactions_self_recursive(ci, top, s); /* Now loop over all other top-level cells for pair interactions. * This mirrors the pair tasks created between top-level cells. */ for (int n = 0; n < s->nr_cells; n++) { /* Handle on the top-level cell and its gravity business */ struct cell *cj = &cells[n]; struct gravity_tensors *const multi_j = cj->grav.multipole; /* Avoid self contributions (already handled above) */ if (top == cj) continue; /* Skip empty cells */ if (multi_j->m_pole.M_000 == 0.f) continue; /* Can we use the mesh for this top-level pair? */ if (cell_can_use_mesh(e, top, cj)) { /* If so, record the mesh interaction */ runner_count_mesh_interaction(ci, top, cj); continue; } /* Can we use M-M for this top-level pair? */ if (cell_can_use_pair_mm(top, cj, e, s, /*use_rebuild_data=*/1, /*is_tree_walk=*/0)) { /* M-M task handles this, nothing to count */ continue; } /* We would create a pair task here, so recurse to count mesh interactions * that arise from task splitting */ runner_count_mesh_interactions_pair_recursive(ci, top, cj, s); } #else error( "This function should not be called without debugging checks or " "force checks enabled!"); #endif } /** * @brief Performs all M-M interactions between a given top-level cell and * all the other top-levels that are far enough. * * @param r The thread #runner. * @param ci The #cell of interest. * @param timer Are we timing this ? */ void runner_do_grav_long_range(struct runner *r, struct cell *ci, const int timer) { TIMER_TIC; struct space *s = r->e->s; /* Is the space periodic? */ const int periodic = s->periodic; /* Anything to do here? */ if (!cell_is_active_gravity(ci, r->e)) return; if (ci->nodeID != engine_rank) error("Non-local cell in long-range gravity task!"); /* Check multipole has been drifted */ if (ci->grav.ti_old_multipole < r->e->ti_current) cell_drift_multipole(ci, r->e); /* Find this cell's top-level (great-)parent */ struct cell *top = ci; while (top->parent != NULL) top = top->parent; /* Call the appropriate interaction function based on the type of the * cell in question. */ if (periodic) { runner_do_grav_long_range_periodic(r, ci, top); } else { runner_do_grav_long_range_non_periodic(r, ci, top); } #if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_GRAVITY_FORCE_CHECKS) /* Count the number of mesh interactions if using the mesh. */ if (periodic) { runner_count_mesh_interactions(r, ci, top); } #endif if (timer) TIMER_TOC(timer_dograv_long_range); }