/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2024 Matthieu Schaller (schaller@strw.leidenuniv.nl)
* Yolan Uyttenhove (Yolan.Uyttenhove@UGent.be)
*
* 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
/* Corresponding header */
#include "cell_grid.h"
/* Local headers */
#include "engine.h"
#include "error.h"
#include "space_getsid.h"
/**
* @brief Recursively free grid memory for cell.
*
* @param c The #cell.
*/
void cell_free_grid_rec(struct cell *c) {
#ifndef MOVING_MESH
/* Nothing to do as we have no tessellations */
#else
#ifdef SWIFT_DEBUG_CHECKS
if (c->grid.construction_level != c && c->grid.voronoi != NULL)
error("Grid allocated, but not on grid construction level!");
#endif
if (c->grid.construction_level == NULL) {
for (int k = 0; k < 8; k++)
if (c->progeny[k] != NULL) cell_free_grid_rec(c->progeny[k]);
} else if (c->grid.construction_level == c) {
cell_free_grid(c);
} else if (c->grid.construction_level != c) {
error("Somehow ended up below grid construction level!");
}
#endif
}
/**
* @brief Updates the grid.self_completeness flag on this cell and its
* sub-cells.
*
* This cell satisfies the local completeness criterion for the Voronoi grid.
*
* A cell is defined as locally complete if, when we would split that cell in
* thirds along each dimension (i.e. in 27 smaller cells), every small cube
* would contain at least one particle.
*
* If a given cell and its direct neighbours on the same level in the AMR tree
* are self-complete, the Voronoi grid of that cell can completely be
* constructed using only particles of this cell and its direct neighbours,
* i.e. by doing a normal SWIFT neighbour loop.
*
* @param c The #cell to be checked
* @param force Whether to forcefully recompute the completeness if it was not
* invalidated.
* */
void cell_grid_update_self_completeness(struct cell *c, int force) {
if (c == NULL) return;
if (!force && (c->grid.self_completeness != grid_invalidated_completeness))
return;
if (c->split) {
int all_complete = 1;
/* recurse */
for (int i = 0; all_complete && i < 8; i++) {
if (c->progeny[i] != NULL) {
cell_grid_update_self_completeness(c->progeny[i], force);
/* As long as all progeny is complete, this cell can safely be split for
* the grid construction (when not considering neighbouring cells) */
all_complete &=
(c->progeny[i]->grid.self_completeness == grid_complete);
}
}
/* If all sub-cells are complete, this cell is also complete. */
if (all_complete) {
c->grid.self_completeness = grid_complete;
/* We set complete to true for now */
c->grid.complete = 1;
/* We are done here */
return;
}
}
/* If this cell is not split, or not all subcells are complete, we need to
* check if this cell is complete by looping over all the particles. */
/* criterion = 0b111_111_111_111_111_111_111_111_111*/
#ifdef HYDRO_DIMENSION_1D
const int criterion = (1 << 3) - 1;
#elif defined(HYDRO_DIMENSION_2D)
const int criterion = (1 << 9) - 1;
#elif defined(HYDRO_DIMENSION_3D)
const int criterion = (1 << 27) - 1;
#else
#error "Unknown hydro dimension"
#endif
int flags = 0;
for (int i = 0; flags != criterion && i < c->hydro.count; i++) {
struct part *p = &c->hydro.parts[i];
int x_bin = (int)(3. * (p->x[0] - c->loc[0]) / c->width[0]);
int y_bin = (int)(3. * (p->x[1] - c->loc[1]) / c->width[1]);
int z_bin = (int)(3. * (p->x[2] - c->loc[2]) / c->width[2]);
if (x_bin >= 0 && x_bin < 3 && y_bin >= 0 && y_bin < 3 && z_bin >= 0 &&
z_bin < 3) {
flags |= 1 << (x_bin + 3 * y_bin + 9 * z_bin);
}
}
/* Set completeness flags accordingly */
if (flags == criterion) {
c->grid.self_completeness = grid_complete;
c->grid.complete = 1;
} else {
c->grid.self_completeness = grid_incomplete;
c->grid.complete = 0;
}
}
/**
* @brief (mapper) Updates the grid.self_completeness flag on this cell and its
* sub-cells.
*
* This function recomputes the self_completeness flag for all cells containing
* particles.
*/
void cell_grid_set_self_completeness_mapper(void *map_data, int num_elements,
void *extra_data) {
/* Extract the engine pointer. */
struct engine *e = (struct engine *)extra_data;
struct space *s = e->s;
const int nodeID = e->nodeID;
struct cell *cells = s->cells_top;
/* Loop through the elements, which are just byte offsets from NULL. */
for (int ind = 0; ind < num_elements; ind++) {
/* Get the cell index. */
const int cid = (size_t)(map_data) + ind;
/* Get the cell */
struct cell *c = &cells[cid];
/* A top level cell can be empty in 1D and 2D simulations, just skip it */
if (c->hydro.count == 0) {
continue;
#ifdef SWIFT_DEBUG_CHECKS
if (hydro_dimension == 3)
error("Found empty top-level cell while running in 3D!");
#endif
}
if (c->nodeID != nodeID) continue;
/* Set the splittable attribute for the moving mesh */
cell_grid_update_self_completeness(c, /*force*/ 1);
}
}
/**
* @brief Updates the grid.completeness flag for the given pair of cells and all
* recursive pairs of sub-cells.
*
* If one of the cells of the pair is not self-complete, or requires a rebuild,
* we mark the other cell in the pair as incomplete (it cannot construct its
* Voronoi grid on that level).
*
*
*
* @param ci The first #cell of the pair to be checked.
* @param cj The second #cell of the pair to be checked. If NULL, only check
* pairs of subcells from ci.
* @param sid The sort_id (direction) of the pair.
* @param e The #engine.
* */
void cell_grid_set_pair_completeness(struct cell *restrict ci,
struct cell *restrict cj, int sid,
const struct engine *e) {
int ci_local = ci->nodeID == e->nodeID;
int cj_local = cj != NULL ? cj->nodeID == e->nodeID : 0;
/* Anything to do here? */
if (!ci_local && !cj_local) return;
/* Self or pair? */
if (cj == NULL) {
/* Self: Here we just need to recurse to hit all the pairs of sub-cells */
if (ci->split) {
/* recurse */
for (int k = 0; k < 7; k++) {
if (ci->progeny[k] != NULL) {
/* Self: Recurse for pairs of sub-cells of this sub-cell */
cell_grid_set_pair_completeness(ci->progeny[k], NULL, 0, e);
/* Recurse for pairs of sub-cells */
for (int l = k + 1; l < 8; l++) {
if (ci->progeny[l] != NULL) {
/* Get sid for pair */
int sid_sub = sub_sid_flag[k][l];
/* Pair: Recurse for pairs of sub-cells of this pair of sub-cells
*/
cell_grid_set_pair_completeness(ci->progeny[k], ci->progeny[l],
sid_sub, e);
}
}
}
}
}
} else {
/* pair: Here we need to recurse further AND check whether one of the
* neighbouring cells invalidates the completeness of the other. */
struct cell_split_pair pairs = cell_split_pairs[sid];
if (ci->split && cj->split) {
/* recurse */
for (int i = 0; i < pairs.count; i++) {
struct cell *ci_sub = ci->progeny[pairs.pairs[i].pid];
struct cell *cj_sub = cj->progeny[pairs.pairs[i].pjd];
if (ci_sub == NULL || cj_sub == NULL) continue;
double shift[3];
int sid_sub =
space_getsid_and_swap_cells(e->s, &ci_sub, &cj_sub, shift);
#ifdef SWIFT_DEBUG_CHECKS
assert(sid_sub == pairs.pairs[i].sid);
#endif
cell_grid_set_pair_completeness(ci_sub, cj_sub, sid_sub, e);
}
} else if (!ci->split && cj->split) {
/* Set the completeness for the sub-cells of cj for this sid to 0 (they
* have no neighbouring cell on the same level for this SID) */
for (int i = 0; i < pairs.count; i++) {
int l = pairs.pairs[i].pjd;
if (cj->progeny[l] != NULL) cj->progeny[l]->grid.complete = 0;
}
} else if (!cj->split && ci->split) {
/* Set the completeness for the sub-cells of ci for this sid to 0 (they
* have no neighbouring cell on the same level for this SID) */
for (int i = 0; i < pairs.count; i++) {
int k = pairs.pairs[i].pid;
if (ci->progeny[k] != NULL) ci->progeny[k]->grid.complete = 0;
}
}
/* Update these cells' completeness flags (i.e. check whether the
* neighbouring cell invalidates completeness)
* We need to use atomics here, since multiple threads may change this at
* the same time. */
if (ci_local) {
atomic_and(&ci->grid.complete,
!cell_grid_pair_invalidates_completeness(ci, cj));
}
if (cj_local) {
atomic_and(&cj->grid.complete,
!cell_grid_pair_invalidates_completeness(cj, ci));
}
}
}
/**
* @brief (mapper) Sets the grid.completeness flag for all cells, by looping
* aggregating the self_completeness flags of all neighbours of each cell.
*
* A cell is considered complete if it and all its neighbours are
* self_complete. The Voronoi grid may be constructed at any level in the AMR
* tree as long as the cell at that level is complete.
* */
void cell_set_grid_completeness_mapper(void *map_data, int num_elements,
void *extra_data) {
/* Extract the engine pointer. */
struct engine *e = (struct engine *)extra_data;
const int periodic = e->s->periodic;
struct space *s = e->s;
const int nodeID = e->nodeID;
const int *cdim = s->cdim;
struct cell *cells = s->cells_top;
/* Loop through the elements, which are just byte offsets from NULL, to set
* the neighbour flags. */
for (int ind = 0; ind < num_elements; ind++) {
/* Get the cell index. */
const int cid = (size_t)(map_data) + ind;
/* Integer indices of the cell in the top-level grid */
const int i = cid / (cdim[1] * cdim[2]);
const int j = (cid / cdim[2]) % cdim[1];
const int k = cid % cdim[2];
/* Get the cell */
struct cell *ci = &cells[cid];
/* Anything to do here? */
if (ci->hydro.count == 0) continue;
const int ci_local = ci->nodeID == nodeID;
/* Update completeness for all the pairs of sub cells of this cell */
if (ci_local) cell_grid_set_pair_completeness(ci, NULL, 0, e);
/* Now loop over all the neighbours of this cell to also update the
* completeness for pairs with this cell and all pairs of sub-cells */
for (int ii = -1; ii < 2; ii++) {
int iii = i + ii;
if (!periodic && (iii < 0 || iii >= cdim[0])) continue;
iii = (iii + cdim[0]) % cdim[0];
for (int jj = -1; jj < 2; jj++) {
int jjj = j + jj;
if (!periodic && (jjj < 0 || jjj >= cdim[1])) continue;
jjj = (jjj + cdim[1]) % cdim[1];
for (int kk = -1; kk < 2; kk++) {
int kkk = k + kk;
if (!periodic && (kkk < 0 || kkk >= cdim[2])) continue;
kkk = (kkk + cdim[2]) % cdim[2];
/* Get the neighbouring cell */
const int cjd = cell_getid(cdim, iii, jjj, kkk);
struct cell *cj = &cells[cjd];
/* Treat pairs only once. */
const int cj_local = cj->nodeID == nodeID;
if (cid >= cjd || cj->hydro.count == 0 || (!ci_local && !cj_local))
continue;
/* Update the completeness flag for this pair of cells and all pair of
* sub-cells */
int sid = (kk + 1) + 3 * ((jj + 1) + 3 * (ii + 1));
const int flip = runner_flip[sid];
sid = sortlistID[sid];
if (flip) {
cell_grid_set_pair_completeness(cj, ci, sid, e);
} else {
cell_grid_set_pair_completeness(ci, cj, sid, e);
}
}
}
} /* Now loop over all the neighbours of this cell */
} /* Loop through the elements, which are just byte offsets from NULL. */
}
/**
* @brief Select a suitable construction level for the Voronoi grid.
*
* The Voronoi grid is constructed at the lowest level for which the cell
* has more than #space_grid_split_threshold hydro particles *and* is marked
* as complete for the Voronoi construction.
*
* Cells below the construction level store a pointer to its higher level cell
* at the construction level.
*
* @param c The #cell
* @param construction_level NULL, if we are yet to encounter the suitable
* construction level, or a pointer to the parent-cell of c at the construction
* level.
*/
void cell_set_grid_construction_level(struct cell *c,
struct cell *construction_level) {
/* Above construction level? */
if (construction_level == NULL) {
/* Check if we can split this cell (i.e. all sub-cells are complete) */
int splittable = c->split && c->hydro.count > space_grid_split_threshold;
if (!c->grid.complete) {
/* Are we on the top level? */
if (c->top == c) {
warning("Found incomplete top level cell!");
splittable = 0;
} else {
error("Found incomplete cell above construction level!");
}
}
for (int k = 0; splittable && k < 8; k++) {
if (c->progeny[k] != NULL) splittable &= c->progeny[k]->grid.complete;
}
if (!splittable) {
/* This is the first time we encounter an unsplittable cell, meaning that
* it has too few particles to be split further or one of its progenitors
* is not complete. I.e. we have arrived at the construction level! */
construction_level = c;
}
}
/* Set the construction level of this cell */
c->grid.construction_level = construction_level;
/* Recurse. */
if (c->split)
for (int k = 0; k < 8; k++) {
if (c->progeny[k] != NULL)
cell_set_grid_construction_level(c->progeny[k], construction_level);
}
}
void cell_set_grid_construction_level_mapper(void *map_data, int num_elements,
void *extra_data) {
/* Extract the engine pointer. */
struct engine *e = (struct engine *)extra_data;
struct space *s = e->s;
const int nodeID = e->nodeID;
struct cell *cells = s->cells_top;
/* Loop through the elements, which are just byte offsets from NULL, to set
* the neighbour flags. */
for (int ind = 0; ind < num_elements; ind++) {
/* Get the cell index. */
const int cid = (size_t)(map_data) + ind;
/* Get the cell */
struct cell *ci = &cells[cid];
/* Anything to do here? */
if (ci->hydro.count == 0) continue;
const int ci_local = ci->nodeID == nodeID;
/* This cell's completeness flags are now set all the way down the cell
* hierarchy. We can now set the construction level. */
if (ci_local) {
cell_set_grid_construction_level(ci, NULL);
}
}
}