/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk)
* Matthieu Schaller (schaller@strw.leidenuniv.nl)
* 2015 Peter W. Draper (p.w.draper@durham.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
/* This object's header. */
#include "space.h"
/* Local headers. */
#include "cell.h"
#include "engine.h"
#include "scheduler.h"
#include "zoom_region/zoom.h"
/**
* @brief Get the current h_max.
*
* If local_cells_with_particles_top is not NULL, then we use the list of local
* non-empty top-level cells. If local_cells_with_particles_top is NULL, then we
* use all the top-level cells. If the top-level cells are not allocated, then
* we run through the particles.
*
* @param s The #space.
* @param local_cells_with_particles_top Indices of local non-empty TL cells.
* @param nr_local_cells_with_particles Number of local non-empty TL cells.
* @param nr_cells Number of TL cells to run over.
* @param cell_min The minimum cell size.
*
* @return The current h_max.
*
*/
static float space_get_current_hmax(
struct space *s, const int *const local_cells_with_particles_top,
const int nr_local_cells_with_particles, const int nr_cells,
const double cell_min, const int verbose) {
const size_t nr_parts = s->nr_parts;
const size_t nr_sparts = s->nr_sparts;
const size_t nr_bparts = s->nr_bparts;
const size_t nr_sinks = s->nr_sinks;
/* Run through the cells and get the current h_max. */
float h_max = cell_min / kernel_gamma / space_stretch;
if (nr_parts > 0) {
/* Can we use the list of local non-empty top-level cells? */
if (local_cells_with_particles_top != NULL) {
for (int k = 0; k < nr_local_cells_with_particles; ++k) {
const struct cell *c = &s->cells_top[local_cells_with_particles_top[k]];
if (c->hydro.h_max > h_max) {
h_max = c->hydro.h_max;
}
if (c->stars.h_max > h_max) {
h_max = c->stars.h_max;
}
if (c->black_holes.h_max > h_max) {
h_max = c->black_holes.h_max;
}
if (c->sinks.h_max > h_max) {
h_max = c->sinks.h_max;
}
}
/* Can we instead use all the top-level cells? */
} else if (s->cells_top != NULL) {
for (int k = 0; k < nr_cells; k++) {
const struct cell *c = &s->cells_top[k];
if (c->nodeID == engine_rank && c->hydro.h_max > h_max) {
h_max = c->hydro.h_max;
}
if (c->nodeID == engine_rank && c->stars.h_max > h_max) {
h_max = c->stars.h_max;
}
if (c->nodeID == engine_rank && c->black_holes.h_max > h_max) {
h_max = c->black_holes.h_max;
}
if (c->nodeID == engine_rank && c->sinks.h_max > h_max) {
h_max = c->sinks.h_max;
}
}
/* Last option: run through the particles */
} else {
for (size_t k = 0; k < nr_parts; k++) {
if (s->parts[k].h > h_max) h_max = s->parts[k].h;
}
for (size_t k = 0; k < nr_sparts; k++) {
if (s->sparts[k].h > h_max) h_max = s->sparts[k].h;
}
for (size_t k = 0; k < nr_bparts; k++) {
if (s->bparts[k].h > h_max) h_max = s->bparts[k].h;
}
for (size_t k = 0; k < nr_sinks; k++) {
if (s->sinks[k].h > h_max) h_max = s->sinks[k].h;
}
}
}
/* If we are running in parallel, make sure everybody agrees on
how large the largest cell should be. */
#ifdef WITH_MPI
{
float buff;
if (MPI_Allreduce(&h_max, &buff, 1, MPI_FLOAT, MPI_MAX, MPI_COMM_WORLD) !=
MPI_SUCCESS)
error("Failed to aggregate the rebuild flag across nodes.");
h_max = buff;
}
#endif
if (verbose) message("h_max is %.3e (cell_min=%.3e).", h_max, cell_min);
return h_max;
}
/**
* @brief Compute the new putative cell dimensions from the current h_max.
*
* @param s The #space.
* @param h_max The current h_max.
* @param cdim The new putative cell dimensions to populate.
*/
static void space_new_cdim_from_hmax(const struct space *s, const float h_max,
int *cdim) {
/* Get the right minimum cell size. */
double cell_min;
double dim[3];
if (!s->with_zoom_region) {
cell_min = s->cell_min;
dim[0] = s->dim[0];
dim[1] = s->dim[1];
dim[2] = s->dim[2];
} else {
cell_min = s->zoom_props->cell_min;
dim[0] = s->zoom_props->dim[0];
dim[1] = s->zoom_props->dim[1];
dim[2] = s->zoom_props->dim[2];
}
/* Compute the new putative cell dimensions. */
cdim[0] =
(int)floor(dim[0] / fmax(h_max * kernel_gamma * space_stretch, cell_min));
cdim[1] =
(int)floor(dim[1] / fmax(h_max * kernel_gamma * space_stretch, cell_min));
cdim[2] =
(int)floor(dim[2] / fmax(h_max * kernel_gamma * space_stretch, cell_min));
/* Check that we have at least 1 cell in each dimension */
if (cdim[0] == 0 || cdim[1] == 0 || cdim[2] == 0) {
error(
"Top level cell dimension of size 0 detected (cdim = [%i %i "
"%i])!\nThis usually indicates a problem with the initial smoothing "
"lengths of the particles, e.g. a smoothing length that is comparable "
"in size to the box size.",
cdim[0], cdim[1], cdim[2]);
}
/* Check if we have enough cells for periodicity. (In the zoom case we
* have to check the background cdim not the hmax defined one.)*/
if (s->periodic &&
((!s->with_zoom_region && (cdim[0] < 3 || cdim[1] < 3 || cdim[2] < 3)) ||
(s->with_zoom_region &&
(s->cdim[0] < 3 || s->cdim[1] < 3 || s->cdim[2] < 3)))) {
error(
"Must have at least 3 cells in each spatial dimension when periodicity "
"is switched on.\nThis error is often caused by any of the "
"followings:\n"
" - too few particles to generate a sensible grid,\n"
" - the initial value of 'Scheduler:max_top_level_cells' is too "
"small,\n"
" - the (minimal) time-step is too large leading to particles with "
"predicted smoothing lengths too large for the box size,\n"
" - particles with velocities so large that they move by more than two "
"box sizes per time-step.\n");
}
}
/**
* @brief Test whether we need a regrid.
*
* @param s The #space.
* @param new_cdim The new putative cell dimensions.
*
* @return 1 if we need a regrid, 0 otherwise.
*/
static int space_need_regrid(const struct space *s, const int new_cdim[3]) {
return (new_cdim[0] < s->cdim[0] || new_cdim[1] < s->cdim[1] ||
new_cdim[2] < s->cdim[2]);
}
#ifdef WITH_MPI
/**
* @brief Prepare the new partition.
*
* Populates the old cdim, width and nodeIDs of the cells if we are about to
* modify an existing cell grid.
*
* @param s The #space.
* @param cdim The new putative cell dimensions.
* @param oldwidth The old cell width.
* @param oldcdim The old cell dimensions.
* @param oldnodeIDs The old node IDs.
*
* @return 1 if we are about to allocate new top-level cells without an existing
* one, 0 otherwise.
*/
static int space_prepare_new_partition(const struct space *s, const int cdim[3],
double oldwidth[3], double oldcdim[3],
int *oldnodeIDs) {
/* Are we regridding? (We only want to trigger this code when we already
* have cells defined.) */
if ((!s->with_zoom_region && space_need_regrid(s, cdim)) ||
(s->with_zoom_region && zoom_need_regrid(s, cdim))) {
/* Capture state of current space. */
oldcdim[0] = s->cdim[0];
oldcdim[1] = s->cdim[1];
oldcdim[2] = s->cdim[2];
oldwidth[0] = s->width[0];
oldwidth[1] = s->width[1];
oldwidth[2] = s->width[2];
if ((oldnodeIDs =
(int *)swift_malloc("nodeIDs", sizeof(int) * s->nr_cells)) == NULL)
error("Failed to allocate temporary nodeIDs.");
int cid = 0;
for (int i = 0; i < s->cdim[0]; i++) {
for (int j = 0; j < s->cdim[1]; j++) {
for (int k = 0; k < s->cdim[2]; k++) {
cid = cell_getid(oldcdim, i, j, k);
oldnodeIDs[cid] = s->cells_top[cid].nodeID;
}
}
}
}
/* Are we about to allocate new top level cells without a regrid?
* Can happen when restarting the application. */
return (s->cells_top == NULL && oldnodeIDs == NULL);
}
#endif // WITH_MPI
/**
* @brief Prepare and allocate the top-level cell and pointer arrays.
*
* This function also frees the task arrays, sets the new cell dimensions and
* cell counts on the space, and allocates the top level cells, multipoles and
* indices of local cells.
*
* @param s The #space.
* @param cdim The new cell dimensions.
*/
static void space_prepare_cells(struct space *s, const int cdim[3]) {
/* Free the old cells, if they were allocated. */
if (s->cells_top != NULL) {
space_free_cells(s);
swift_free("local_cells_with_tasks_top", s->local_cells_with_tasks_top);
swift_free("local_cells_top", s->local_cells_top);
swift_free("cells_with_particles_top", s->cells_with_particles_top);
swift_free("local_cells_with_particles_top",
s->local_cells_with_particles_top);
swift_free("cells_top", s->cells_top);
swift_free("cells_top_updated", s->cells_top_updated);
swift_free("multipoles_top", s->multipoles_top);
}
/* Also free the task arrays, these will be regenerated and we can use the
* memory while copying the particle arrays. */
if (s->e != NULL) scheduler_free_tasks(&s->e->sched);
/* When running a zoom we need to avoid overwriting what zoom_region_init has
* already done. */
if (!s->with_zoom_region) {
/* Set the new cell dimensions only if smaller. (In the zoom case this
* is done in zoom_region_init) */
for (int k = 0; k < 3; k++) {
s->cdim[k] = cdim[k];
s->width[k] = s->dim[k] / cdim[k];
s->iwidth[k] = 1.0 / s->width[k];
}
/* Count the number of top level cells. (In the zoom case this is
* calculated in zoom_prepare_cells)*/
s->tot_cells = s->nr_cells = cdim[0] * cdim[1] * cdim[2];
}
}
/**
* @brief Allocate the top-level cells.
*
* This function also allocates the top-level cells, their multipoles, the
* indices of local cells, the indices of local cells with tasks, the indices of
* cells with particles, and the indices of local cells with particles. It also
* sets the cells' locks after allocation.
*
* @param s The #space.
*/
void space_allocate_cells(struct space *s) {
/* Allocate the top level cells array. */
if (swift_memalign("cells_top", (void **)&s->cells_top, cell_align,
s->nr_cells * sizeof(struct cell)) != 0)
error("Failed to allocate top-level cells.");
bzero(s->cells_top, s->nr_cells * sizeof(struct cell));
/* Allocate the multipoles for the top-level cells. */
if (s->with_self_gravity) {
if (swift_memalign("multipoles_top", (void **)&s->multipoles_top,
multipole_align,
s->nr_cells * sizeof(struct gravity_tensors)) != 0)
error("Failed to allocate top-level multipoles.");
bzero(s->multipoles_top, s->nr_cells * sizeof(struct gravity_tensors));
}
if (swift_memalign("cells_top_updated", (void **)&s->cells_top_updated,
cell_align, s->nr_cells * sizeof(char)) != 0)
error("Failed to allocate top-level cells.");
bzero(s->cells_top_updated, s->nr_cells * sizeof(char));
/* Allocate the indices of local cells */
if (swift_memalign("local_cells_top", (void **)&s->local_cells_top,
SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
error("Failed to allocate indices of local top-level cells.");
bzero(s->local_cells_top, s->nr_cells * sizeof(int));
/* Allocate the indices of local cells with tasks */
if (swift_memalign("local_cells_with_tasks_top",
(void **)&s->local_cells_with_tasks_top,
SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
error("Failed to allocate indices of local top-level cells with tasks.");
bzero(s->local_cells_with_tasks_top, s->nr_cells * sizeof(int));
/* Allocate the indices of cells with particles */
if (swift_memalign("cells_with_particles_top",
(void **)&s->cells_with_particles_top,
SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
error("Failed to allocate indices of top-level cells with particles.");
bzero(s->cells_with_particles_top, s->nr_cells * sizeof(int));
/* Allocate the indices of local cells with particles */
if (swift_memalign("local_cells_with_particles_top",
(void **)&s->local_cells_with_particles_top,
SWIFT_STRUCT_ALIGNMENT, s->nr_cells * sizeof(int)) != 0)
error(
"Failed to allocate indices of local top-level cells with "
"particles.");
bzero(s->local_cells_with_particles_top, s->nr_cells * sizeof(int));
/* Set the cells' locks */
for (int k = 0; k < s->nr_cells; k++) {
if (lock_init(&s->cells_top[k].hydro.lock) != 0)
error("Failed to init spinlock for hydro.");
if (lock_init(&s->cells_top[k].hydro.extra_sort_lock) != 0)
error("Failed to init spinlock for hydro extra sort.");
if (lock_init(&s->cells_top[k].grav.plock) != 0)
error("Failed to init spinlock for gravity.");
if (lock_init(&s->cells_top[k].grav.mlock) != 0)
error("Failed to init spinlock for multipoles.");
if (lock_init(&s->cells_top[k].grav.star_formation_lock) != 0)
error("Failed to init spinlock for star formation (gpart).");
if (lock_init(&s->cells_top[k].stars.lock) != 0)
error("Failed to init spinlock for stars.");
if (lock_init(&s->cells_top[k].sinks.lock) != 0)
error("Failed to init spinlock for sinks.");
if (lock_init(&s->cells_top[k].sinks.sink_formation_lock) != 0)
error("Failed to init spinlock for sink formation.");
if (lock_init(&s->cells_top[k].black_holes.lock) != 0)
error("Failed to init spinlock for black holes.");
if (lock_init(&s->cells_top[k].stars.star_formation_lock) != 0)
error("Failed to init spinlock for star formation (spart).");
}
}
/**
* @brief Construct the top-level cells for a uniform box.
*
* @param s The #space.
* @param ti_current The current time.
* @param verbose Are we talking?
*/
static void uniform_construct_tl_cells(struct space *s,
const integertime_t ti_current,
const int verbose) {
/* Get the minimum cell size. */
const float dmin = min3(s->width[0], s->width[1], s->width[2]);
/* Set the cell location and sizes. */
for (int i = 0; i < s->cdim[0]; i++) {
for (int j = 0; j < s->cdim[1]; j++) {
for (int k = 0; k < s->cdim[2]; k++) {
const size_t cid = cell_getid(s->cdim, i, j, k);
struct cell *restrict c = &s->cells_top[cid];
c->loc[0] = i * s->width[0];
c->loc[1] = j * s->width[1];
c->loc[2] = k * s->width[2];
c->width[0] = s->width[0];
c->width[1] = s->width[1];
c->width[2] = s->width[2];
c->dmin = dmin;
c->h_min_allowed = c->dmin * 0.5 * (1. / kernel_gamma);
c->h_max_allowed = c->dmin * (1. / kernel_gamma);
c->depth = 0;
c->split = 0;
c->hydro.count = 0;
c->grav.count = 0;
c->stars.count = 0;
c->sinks.count = 0;
c->top = c;
c->super = c;
c->hydro.super = c;
c->grav.super = c;
c->hydro.ti_old_part = ti_current;
c->grav.ti_old_part = ti_current;
c->stars.ti_old_part = ti_current;
c->sinks.ti_old_part = ti_current;
c->black_holes.ti_old_part = ti_current;
c->grav.ti_old_multipole = ti_current;
#ifdef WITH_MPI
c->mpi.tag = -1;
c->mpi.recv = NULL;
c->mpi.send = NULL;
#endif // WITH_MPI
if (s->with_self_gravity) c->grav.multipole = &s->multipoles_top[cid];
#if defined(SWIFT_DEBUG_CHECKS) || defined(SWIFT_CELL_GRAPH)
cell_assign_top_level_cell_index(c, s);
#endif
}
}
}
}
#ifdef WITH_MPI
/**
* @brief Partition the cells over the nodes.
*
* We either need to modify the existing partition or create one.
*
* NOTE: this will eventually house the logic to also call the zoom equivalent
* of the partitioning functions.
*
* @param s The #space.
* @param oldnodeIDs The old node IDs.
* @param oldwidth The old cell width.
* @param oldcdim The old cell dimensions.
* @param no_regrid Are we about to allocate new top-level cells without an
* existing one?
*/
static void space_partition_cells(struct space *s, int *oldnodeIDs,
double oldwidth[3], double oldcdim[3],
const int no_regrid) {
if (oldnodeIDs != NULL) {
/* We have changed the top-level cell dimension, so need to redistribute
* cells around the nodes. We repartition using the old space node
* positions as a grid to resample. */
if (s->e->nodeID == 0)
message(
"Basic cell dimensions have increased - recalculating the "
"global partition.");
if (!partition_space_to_space(oldwidth, oldcdim, oldnodeIDs, s)) {
/* Failed, try another technique that requires no settings. */
message("Failed to get a new partition, trying less optimal method");
struct partition initial_partition;
#if defined(HAVE_PARMETIS) || defined(HAVE_METIS)
initial_partition.type = INITPART_METIS_NOWEIGHT;
#else
initial_partition.type = INITPART_VECTORIZE;
#endif
partition_initial_partition(&initial_partition, s->e->nodeID,
s->e->nr_nodes, s);
}
/* Re-distribute the particles to their new nodes. */
engine_redistribute(s->e);
/* Make the proxies. */
engine_makeproxies(s->e);
/* Finished with these. */
swift_free("nodeIDs", oldnodeIDs);
} else if (no_regrid && s->e != NULL) {
/* If we have created the top-levels cells and not done an initial
* partition (can happen when restarting), then the top-level cells
* are not assigned to a node, we must do that and then associate the
* particles with the cells. Note requires that
* partition_store_celllist() was called once before, or just before
* dumping the restart files.*/
partition_restore_celllist(s, s->e->reparttype);
/* Now re-distribute the particles, should just add to cells? */
engine_redistribute(s->e);
/* Make the proxies. */
engine_makeproxies(s->e);
}
}
#endif // WITH_MPI
/**
* @brief Re-build the top-level cell grid in a uniform box.
*
* @param s The #space.
* @param verbose Print messages to stdout or not.
*/
void space_regrid(struct space *s, int verbose) {
const ticks tic = getticks();
const integertime_t ti_current = (s->e != NULL) ? s->e->ti_current : 0;
/* Get the current h_max. (In zoom land we only care about zoom cells.) */
float h_max;
if (!s->with_zoom_region) {
h_max = space_get_current_hmax(s, s->local_cells_with_particles_top,
s->nr_local_cells_with_particles,
s->nr_cells, s->cell_min, verbose);
} else {
h_max = space_get_current_hmax(
s, s->zoom_props->local_zoom_cells_with_particles_top,
s->zoom_props->nr_local_zoom_cells_with_particles,
s->zoom_props->nr_zoom_cells, s->zoom_props->cell_min, verbose);
}
/* When running a zoom region if this is our first regrid then we need to get
* the zoom region geometry before moving on. */
if (s->with_zoom_region && s->cells_top == NULL) {
zoom_region_init(s, verbose);
}
/* Get the new putative cell dimensions. */
int cdim[3];
space_new_cdim_from_hmax(s, h_max, cdim);
#ifdef WITH_MPI
/* In MPI-Land, changing the top-level cell size requires that the
* global partition is recomputed and the particles redistributed.
* Be prepared to do that. */
double oldwidth[3] = {0., 0., 0.};
double oldcdim[3] = {0., 0., 0.};
int *oldnodeIDs = NULL;
const int no_regrid =
space_prepare_new_partition(s, cdim, oldwidth, oldcdim, oldnodeIDs);
#endif
/* Do we need to re-build the upper-level cells? */
if (s->cells_top == NULL ||
(!s->with_zoom_region && space_need_regrid(s, cdim)) ||
(s->with_zoom_region && zoom_need_regrid(s, cdim))) {
#ifdef SWIFT_DEBUG_CHECKS
/* Be verbose about this. */
if (!s->with_zoom_region) {
message("(re)griding space cdim=(%d %d %d)", cdim[0], cdim[1], cdim[2]);
} else {
message("(re)griding space zoom_cdim=(%d %d %d)", cdim[0], cdim[1],
cdim[2]);
}
fflush(stdout);
#endif
/* Prepare the cell and pointer arrays. This will also free tasks and set
* the cdim, width, iwidth and cell counts on the space. */
space_prepare_cells(s, cdim);
/* If running a zoom reigon we need to prepare the zoom region and
* all the cell grids beyond the unform case. */
if (s->with_zoom_region) {
zoom_prepare_cells(s, cdim, verbose);
}
/* Allocate the cells. */
space_allocate_cells(s);
/* If running a zoom region we need to allocate the zoom specific arrays
* too. */
if (s->with_zoom_region) {
zoom_allocate_cells(s);
}
/* Construct the top-level cells using the appropriate function. */
if (!s->with_zoom_region) {
uniform_construct_tl_cells(s, ti_current, verbose);
} else {
zoom_construct_tl_cells(s, ti_current, verbose);
}
/* Be verbose about the change. */
if (verbose && !s->with_zoom_region)
message("Set cell dimensions to [ %i %i %i ].", cdim[0], cdim[1],
cdim[2]);
#ifdef WITH_MPI
/* Partition the cells. */
space_partition_cells(s, oldnodeIDs, oldwidth, oldcdim, no_regrid);
#endif
} /* re-build upper-level cells? */
else { /* Otherwise, just clean up the cells. */
/* Free the old sub-cells, if they were allocated, and reinitialise the top
* level cells. */
space_free_cells(s);
#ifdef SWIFT_DEBUG_CHECKS
/* Check the cell types counts against what we have now. */
int nr_zoom_cells = 0;
int nr_buffer_cells = 0;
int nr_bkg_cells = 0;
for (int k = 0; k < s->nr_cells; k++) {
if (s->cells_top[k].type == cell_type_zoom) {
nr_zoom_cells++;
} else if (s->cells_top[k].type == cell_type_buffer) {
nr_buffer_cells++;
} else if (s->cells_top[k].type == cell_type_bkg) {
nr_bkg_cells++;
}
}
if (s->with_zoom_region &&
(nr_zoom_cells != s->zoom_props->nr_zoom_cells ||
nr_buffer_cells != s->zoom_props->nr_buffer_cells ||
nr_bkg_cells != s->zoom_props->nr_bkg_cells)) {
error(
"The number of zoom cells (%d), buffer cells (%d) or background "
"cells (%d) does not match the expected values (%d, %d, %d).",
nr_zoom_cells, nr_buffer_cells, nr_bkg_cells,
s->zoom_props->nr_zoom_cells, s->zoom_props->nr_buffer_cells,
s->zoom_props->nr_bkg_cells);
}
#endif
}
/* When running with a zoom region we need to check we have a big enough mesh
* for the zoom cells, both initially and if the cells have been modified.
* NOTE: Sadly we have to wait to do this until the engine is attached to the
* space before we can make this check without breaking the initialisation
* order, or passing around extra information. Although annoying this just
* means this error could be triggered after everything is set up instead of
* during set up at the beginning of a run. */
if (s->periodic && s->with_zoom_region && s->e != NULL &&
s->dim[0] / s->e->gravity_properties->mesh_size >
s->zoom_props->width[0]) {
error(
"Mesh too small given the size of top-level zoom cells (width= "
"%.2f). Should be at least %d cells wide (Currently: %d).",
s->zoom_props->width[0], (int)(s->dim[0] / s->zoom_props->width[0]),
s->e->gravity_properties->mesh_size);
}
if (verbose)
message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
clocks_getunit());
}