/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2019 Stuart McAlpine (stuart.mcalpine@helsinki.fi)
* 2024 Will J. 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 */
#include
/* Includes */
#include
/* Local includes */
#include "cell.h"
#include "engine.h"
#include "gravity_properties.h"
#include "space.h"
#include "timers.h"
#include "zoom.h"
/* mpi headers. */
#ifdef WITH_MPI
#include
#endif
/* Declare the task diff grav constant. */
int zoom_bkg_subdepth_diff_grav = zoom_bkg_subdepth_diff_grav_default;
/**
* @brief Read parameter file for "ZoomRegion" properties.
*
* @param params Swift parameter structure.
* @param props The zoom properties struct.
*/
void zoom_parse_params(struct swift_params *params,
struct zoom_region_properties *props) {
/* Set the zoom cdim. */
props->zoom_cell_depth =
parser_get_opt_param_int(params, "ZoomRegion:zoom_top_level_depth", 2);
/* Set the target background cdim, default is a negative value so that if no
* value is given for a target then the zoom region defines the background
* cell size. */
int bkg_cdim =
parser_get_opt_param_int(params, "ZoomRegion:bkg_top_level_cells",
space_max_top_level_cells_default);
for (int i = 0; i < 3; i++) {
props->bkg_cdim[i] = bkg_cdim;
}
/* Get the ratio between the zoom region size and buffer cell size.
* Ignored if buffer cells aren't needed. */
props->buffer_cell_depth =
parser_get_opt_param_int(params, "ZoomRegion:buffer_top_level_depth", 0);
/* Ensure the buffer cell depth is less than the zoom cell depth. */
if (props->buffer_cell_depth > props->zoom_cell_depth) {
error("Buffer cell depth must be less than the zoom cell depth.");
}
/* Extract the zoom width boost factor (used to define the buffer around the
* zoom region). */
props->user_region_pad_factor =
parser_get_opt_param_float(params, "ZoomRegion:region_pad_factor", 1.1);
/* Extract the depth we'll split neighbour cells to. */
props->neighbour_max_tree_depth = parser_get_opt_param_int(
params, "ZoomRegion:neighbour_max_tree_depth", -1);
/* Extract the minimum difference between the task level and the leaves
* for background cells. */
zoom_bkg_subdepth_diff_grav =
parser_get_opt_param_int(params, "ZoomRegion:bkg_subdepth_diff_grav",
zoom_bkg_subdepth_diff_grav_default);
/* Extract the maximum shift of the zoom region we will allow in units of
* the zoom region extent. */
props->max_com_dx =
parser_get_opt_param_float(params, "ZoomRegion:max_com_dx", 0.1);
}
struct region_dim_data {
double min_bounds[3];
double max_bounds[3];
double com[3];
double vcom[3];
double mtot;
struct space *s;
};
/**
* @brief Mapper to calculate the CoM and boundaries of the high resolution
* particle distribution.
*
* @param map_data The #gpart array.
* @param num_elements The number of g-particles.
* @param extra_data The #region_dim_data struct to store the results in.
*/
void zoom_get_region_dim_and_shift_mapper(void *map_data, int num_elements,
void *extra_data) {
/* Unpack the mapper data. */
struct gpart *gparts = (struct gpart *)map_data;
struct region_dim_data *data = (struct region_dim_data *)extra_data;
struct space *s = data->s;
/* Set up local values to update. */
double min_bounds[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
double max_bounds[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
double com[3] = {0.0, 0.0, 0.0};
double vcom[3] = {0.0, 0.0, 0.0};
double mtot = 0.0;
/* Loop over the particles. */
for (int k = 0; k < num_elements; k++) {
/* Skip background particles. */
if (gparts[k].type == swift_type_dark_matter_background) {
continue;
}
/* Unpack the particle positions. */
/* NOTE: these will have already been shifted by the user requested amount
* in space_init if shift in the parameter file is non-zero. */
const double x = gparts[k].x[0];
const double y = gparts[k].x[1];
const double z = gparts[k].x[2];
/* Wrap if periodic. */
if (s->periodic) {
box_wrap(x, 0.0, s->dim[0]);
box_wrap(y, 0.0, s->dim[1]);
box_wrap(z, 0.0, s->dim[2]);
}
/* Ammend boundaries for this particle. */
if (x > max_bounds[0]) max_bounds[0] = x;
if (y > max_bounds[1]) max_bounds[1] = y;
if (z > max_bounds[2]) max_bounds[2] = z;
if (x < min_bounds[0]) min_bounds[0] = x;
if (y < min_bounds[1]) min_bounds[1] = y;
if (z < min_bounds[2]) min_bounds[2] = z;
/* Total up mass and position for COM. */
mtot += s->gparts[k].mass;
com[0] += x * gparts[k].mass;
com[1] += y * gparts[k].mass;
com[2] += z * gparts[k].mass;
/* Velocity COM. */
vcom[0] += gparts[k].v_full[0] * gparts[k].mass;
vcom[1] += gparts[k].v_full[1] * gparts[k].mass;
vcom[2] += gparts[k].v_full[2] * gparts[k].mass;
}
/* Atomically update the results. */
atomic_add_d(&data->mtot, mtot);
atomic_add_d(&data->com[0], com[0]);
atomic_add_d(&data->com[1], com[1]);
atomic_add_d(&data->com[2], com[2]);
atomic_add_d(&data->vcom[0], vcom[0]);
atomic_add_d(&data->vcom[1], vcom[1]);
atomic_add_d(&data->vcom[2], vcom[2]);
atomic_min_d(&data->min_bounds[0], min_bounds[0]);
atomic_min_d(&data->min_bounds[1], min_bounds[1]);
atomic_min_d(&data->min_bounds[2], min_bounds[2]);
atomic_max_d(&data->max_bounds[0], max_bounds[0]);
atomic_max_d(&data->max_bounds[1], max_bounds[1]);
atomic_max_d(&data->max_bounds[2], max_bounds[2]);
}
/**
* @brief Compute the zoom region centre and boundaries.
*
* Finds the dimensions of the high resolution particle distribution and
* computes the necessary shift to shift the zoom region to the centre of the
* box. This shift is stored to be applied in space_init and for
* transformation when writing out.
*
* @param s The space
*/
void zoom_get_region_dim_and_shift(struct space *s, const int verbose) {
const ticks tic = getticks();
/* Initialise values we will need. */
const size_t nr_gparts = s->nr_gparts;
double midpoint[3] = {0.0, 0.0, 0.0};
double ini_dims[3] = {0.0, 0.0, 0.0};
const double box_mid[3] = {s->dim[0] / 2.0, s->dim[1] / 2.0, s->dim[2] / 2.0};
/* Allocate the mapper data. */
struct region_dim_data *reg_data =
(struct region_dim_data *)malloc(sizeof(struct region_dim_data));
if (reg_data == NULL) {
error("Failed to allocate memory for zoom region dimension data.");
}
/* Initialise the mapper data. */
reg_data->mtot = 0.0;
for (int i = 0; i < 3; i++) {
reg_data->min_bounds[i] = FLT_MAX;
reg_data->max_bounds[i] = -FLT_MAX;
reg_data->com[i] = 0.0;
reg_data->vcom[i] = 0.0;
}
reg_data->s = s;
/* Find the min/max location in each dimension for each
* high resolution gravity particle (non-background), and their COM. */
/* If we don't have the engine yet we have to call the mapper in serial. */
if (s->e == NULL) {
zoom_get_region_dim_and_shift_mapper(s->gparts, nr_gparts, reg_data);
} else {
struct engine *e = s->e;
threadpool_map(&e->threadpool, zoom_get_region_dim_and_shift_mapper,
s->gparts, nr_gparts, sizeof(struct gpart),
threadpool_auto_chunk_size, reg_data);
}
/* Unpack the results. */
double *min_bounds = reg_data->min_bounds;
double *max_bounds = reg_data->max_bounds;
double *com = reg_data->com;
double *vcom = reg_data->vcom;
double mtot = reg_data->mtot;
#ifdef WITH_MPI
/* Share answers amoungst nodes. */
/* Boundary. */
MPI_Allreduce(MPI_IN_PLACE, &min_bounds[0], 3, MPI_DOUBLE, MPI_MIN,
MPI_COMM_WORLD);
MPI_Allreduce(MPI_IN_PLACE, &max_bounds[1], 3, MPI_DOUBLE, MPI_MAX,
MPI_COMM_WORLD);
/* CoM and bulk velocity. */
MPI_Allreduce(MPI_IN_PLACE, com, 3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce(MPI_IN_PLACE, vcom, 3, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
MPI_Allreduce(MPI_IN_PLACE, &mtot, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
#endif
/* Finalize CoM calcuation. */
const double imass = 1.0 / mtot;
com[0] *= imass;
com[1] *= imass;
com[2] *= imass;
vcom[0] *= imass;
vcom[1] *= imass;
vcom[2] *= imass;
/* Store the CoM and bulk velocity in the zoom properties. */
for (int i = 0; i < 3; i++) {
s->zoom_props->com[i] = com[i];
s->zoom_props->vcom[i] = vcom[i];
}
/* Store the velocity shift to be applied to the zoom region. */
for (int i = 0; i < 3; i++) {
s->zoom_props->zoom_vel_shift[i] = -vcom[i];
}
/* Get the initial dimensions and midpoint. */
for (int i = 0; i < 3; i++) {
ini_dims[i] = max_bounds[i] - min_bounds[i];
midpoint[i] = min_bounds[i] + (ini_dims[i] / 2.0);
}
/* Calculate the shift needed to place the mid point of the high res
* particles at the centre of the box. This shift is applied to the
* particles in space_init in space.c */
for (int i = 0; i < 3; i++) {
s->zoom_props->zoom_shift[i] = box_mid[i] - midpoint[i];
}
/* We shouldn't shift if the shift is incremental. */
for (int i = 0; i < 3; i++) {
if (fabs(s->zoom_props->zoom_shift[i]) < 0.01 * s->dim[i]) {
s->zoom_props->zoom_shift[i] = 0.0;
}
}
/* If the volume isn't periodic then we can't shift. */
if (!s->periodic) {
for (int i = 0; i < 3; i++) {
if (fabs(s->zoom_props->zoom_shift[i]) > 0.01 * s->dim[i]) {
error(
"Cannot shift the zoom region to the centre of the box "
"when the box is not periodic. Centre the CoM of the high "
"resolution particles in the box. (shift=[%f, %f, %f], dim=[%f, "
"%f, %f])",
s->zoom_props->zoom_shift[0], s->zoom_props->zoom_shift[1],
s->zoom_props->zoom_shift[2], s->dim[0], s->dim[1], s->dim[2]);
}
}
}
/* Let's shift the COM.
* NOTE: boundaries are recalculated relative to box centre later. */
for (int i = 0; i < 3; i++)
s->zoom_props->com[i] += s->zoom_props->zoom_shift[i];
/* Store the particle extent in the zoom properties. */
for (int i = 0; i < 3; i++) s->zoom_props->part_dim[i] = ini_dims[i];
if (verbose) {
message("Computing high resolution particle dim and shift took %f %s",
clocks_from_ticks(getticks() - tic), clocks_getunit());
message("Current high resolution particle extent is [%f, %f, %f]",
ini_dims[0], ini_dims[1], ini_dims[2]);
message("Current high resolution particle CoM is [%f, %f, %f]",
s->zoom_props->com[0], s->zoom_props->com[1],
s->zoom_props->com[2]);
message("Particle shift to box centre is [%f, %f, %f] (not yet applied)",
s->zoom_props->zoom_shift[0], s->zoom_props->zoom_shift[1],
s->zoom_props->zoom_shift[2]);
}
free(reg_data);
}
/**
* @brief Mapper function to apply the zoom shift to parts.
*
* @param map_data The #part array.
* @param num_elements The number of particles.
* @param extra_data The zoom properties struct.
*/
void zoom_apply_zoom_shift_to_part_mapper(void *map_data, int num_elements,
void *extra_data) {
struct part *parts = (struct part *)map_data;
struct zoom_region_properties *zp =
(struct zoom_region_properties *)extra_data;
for (int k = 0; k < num_elements; k++) {
parts[k].x[0] += zp->zoom_shift[0];
parts[k].x[1] += zp->zoom_shift[1];
parts[k].x[2] += zp->zoom_shift[2];
parts[k].v[0] += zp->zoom_vel_shift[0];
parts[k].v[1] += zp->zoom_vel_shift[1];
parts[k].v[2] += zp->zoom_vel_shift[2];
}
}
/**
* @brief Mapper function to apply the zoom shift to gparts.
*
* @param map_data The #gpart array.
* @param num_elements The number of particles.
* @param extra_data The zoom properties struct.
*/
void zoom_apply_zoom_shift_to_gpart_mapper(void *map_data, int num_elements,
void *extra_data) {
struct gpart *gparts = (struct gpart *)map_data;
struct zoom_region_properties *zp =
(struct zoom_region_properties *)extra_data;
for (int k = 0; k < num_elements; k++) {
gparts[k].x[0] += zp->zoom_shift[0];
gparts[k].x[1] += zp->zoom_shift[1];
gparts[k].x[2] += zp->zoom_shift[2];
gparts[k].v_full[0] += zp->zoom_vel_shift[0];
gparts[k].v_full[1] += zp->zoom_vel_shift[1];
gparts[k].v_full[2] += zp->zoom_vel_shift[2];
}
}
/**
* @brief Mapper function to apply the zoom shift to sparts.
*
* @param map_data The #spart array.
* @param num_elements The number of particles.
* @param extra_data The zoom properties struct.
*/
void zoom_apply_zoom_shift_to_spart_mapper(void *map_data, int num_elements,
void *extra_data) {
struct spart *sparts = (struct spart *)map_data;
struct zoom_region_properties *zp =
(struct zoom_region_properties *)extra_data;
for (int k = 0; k < num_elements; k++) {
sparts[k].x[0] += zp->zoom_shift[0];
sparts[k].x[1] += zp->zoom_shift[1];
sparts[k].x[2] += zp->zoom_shift[2];
sparts[k].v[0] += zp->zoom_vel_shift[0];
sparts[k].v[1] += zp->zoom_vel_shift[1];
sparts[k].v[2] += zp->zoom_vel_shift[2];
}
}
/**
* @brief Mapper function to apply the zoom shift to bparts.
*
* @param map_data The #bpart array.
* @param num_elements The number of particles.
* @param extra_data The zoom properties struct.
*/
void zoom_apply_zoom_shift_to_bpart_mapper(void *map_data, int num_elements,
void *extra_data) {
struct bpart *bparts = (struct bpart *)map_data;
struct zoom_region_properties *zp =
(struct zoom_region_properties *)extra_data;
for (int k = 0; k < num_elements; k++) {
bparts[k].x[0] += zp->zoom_shift[0];
bparts[k].x[1] += zp->zoom_shift[1];
bparts[k].x[2] += zp->zoom_shift[2];
bparts[k].v[0] += zp->zoom_vel_shift[0];
bparts[k].v[1] += zp->zoom_vel_shift[1];
bparts[k].v[2] += zp->zoom_vel_shift[2];
}
}
/**
* @brief Mapper function to apply the zoom shift to sinks.
*
* @param map_data The #sink array.
* @param num_elements The number of particles.
* @param extra_data The zoom properties struct.
*/
void zoom_apply_zoom_shift_to_sink_mapper(void *map_data, int num_elements,
void *extra_data) {
struct sink *sinks = (struct sink *)map_data;
struct zoom_region_properties *zp =
(struct zoom_region_properties *)extra_data;
for (int k = 0; k < num_elements; k++) {
sinks[k].x[0] += zp->zoom_shift[0];
sinks[k].x[1] += zp->zoom_shift[1];
sinks[k].x[2] += zp->zoom_shift[2];
sinks[k].v[0] += zp->zoom_vel_shift[0];
sinks[k].v[1] += zp->zoom_vel_shift[1];
sinks[k].v[2] += zp->zoom_vel_shift[2];
}
}
/**
* @brief Apply the zoom shift to all particles in the space.
*
* NOTE: could probably be a threadpool in the future.
*
* @param s The space
*/
void zoom_apply_zoom_shift_to_particles(struct space *s, const int verbose) {
const ticks tic = getticks();
/* If no shift is needed, return. */
if (s->zoom_props->zoom_shift[0] == 0.0 &&
s->zoom_props->zoom_shift[1] == 0.0 &&
s->zoom_props->zoom_shift[2] == 0.0 &&
s->zoom_props->zoom_vel_shift[0] == 0.0 &&
s->zoom_props->zoom_vel_shift[1] == 0.0 &&
s->zoom_props->zoom_vel_shift[2] == 0.0) {
return;
}
/* Apply the shift to the particles (if we don't have the engine yet we
* have to loop over in serial). */
if (s->e == NULL) {
for (size_t i = 0; i < s->nr_parts; i++) {
s->parts[i].x[0] += s->zoom_props->zoom_shift[0];
s->parts[i].x[1] += s->zoom_props->zoom_shift[1];
s->parts[i].x[2] += s->zoom_props->zoom_shift[2];
s->parts[i].v[0] += s->zoom_props->zoom_vel_shift[0];
s->parts[i].v[1] += s->zoom_props->zoom_vel_shift[1];
s->parts[i].v[2] += s->zoom_props->zoom_vel_shift[2];
}
for (size_t i = 0; i < s->nr_gparts; i++) {
s->gparts[i].x[0] += s->zoom_props->zoom_shift[0];
s->gparts[i].x[1] += s->zoom_props->zoom_shift[1];
s->gparts[i].x[2] += s->zoom_props->zoom_shift[2];
s->gparts[i].v_full[0] += s->zoom_props->zoom_vel_shift[0];
s->gparts[i].v_full[1] += s->zoom_props->zoom_vel_shift[1];
s->gparts[i].v_full[2] += s->zoom_props->zoom_vel_shift[2];
}
for (size_t i = 0; i < s->nr_sparts; i++) {
s->sparts[i].x[0] += s->zoom_props->zoom_shift[0];
s->sparts[i].x[1] += s->zoom_props->zoom_shift[1];
s->sparts[i].x[2] += s->zoom_props->zoom_shift[2];
s->sparts[i].v[0] += s->zoom_props->zoom_vel_shift[0];
s->sparts[i].v[1] += s->zoom_props->zoom_vel_shift[1];
s->sparts[i].v[2] += s->zoom_props->zoom_vel_shift[2];
}
for (size_t i = 0; i < s->nr_bparts; i++) {
s->bparts[i].x[0] += s->zoom_props->zoom_shift[0];
s->bparts[i].x[1] += s->zoom_props->zoom_shift[1];
s->bparts[i].x[2] += s->zoom_props->zoom_shift[2];
s->bparts[i].v[0] += s->zoom_props->zoom_vel_shift[0];
s->bparts[i].v[1] += s->zoom_props->zoom_vel_shift[1];
s->bparts[i].v[2] += s->zoom_props->zoom_vel_shift[2];
}
for (size_t i = 0; i < s->nr_sinks; i++) {
s->sinks[i].x[0] += s->zoom_props->zoom_shift[0];
s->sinks[i].x[1] += s->zoom_props->zoom_shift[1];
s->sinks[i].x[2] += s->zoom_props->zoom_shift[2];
s->sinks[i].v[0] += s->zoom_props->zoom_vel_shift[0];
s->sinks[i].v[1] += s->zoom_props->zoom_vel_shift[1];
s->sinks[i].v[2] += s->zoom_props->zoom_vel_shift[2];
}
} else {
struct engine *e = s->e;
if (s->nr_parts > 0)
threadpool_map(&e->threadpool, zoom_apply_zoom_shift_to_part_mapper,
s->parts, s->nr_parts, sizeof(struct part),
threadpool_auto_chunk_size, s->zoom_props);
if (s->nr_gparts > 0)
threadpool_map(&e->threadpool, zoom_apply_zoom_shift_to_gpart_mapper,
s->gparts, s->nr_gparts, sizeof(struct gpart),
threadpool_auto_chunk_size, s->zoom_props);
if (s->nr_sparts > 0)
threadpool_map(&e->threadpool, zoom_apply_zoom_shift_to_spart_mapper,
s->sparts, s->nr_sparts, sizeof(struct spart),
threadpool_auto_chunk_size, s->zoom_props);
if (s->nr_bparts > 0)
threadpool_map(&e->threadpool, zoom_apply_zoom_shift_to_bpart_mapper,
s->bparts, s->nr_bparts, sizeof(struct bpart),
threadpool_auto_chunk_size, s->zoom_props);
if (s->nr_sinks > 0)
threadpool_map(&e->threadpool, zoom_apply_zoom_shift_to_sink_mapper,
s->sinks, s->nr_sinks, sizeof(struct sink),
threadpool_auto_chunk_size, s->zoom_props);
}
/* Store what we applied to write out later and zero the shift to avoid
* pointless reapplication. */
for (int i = 0; i < 3; i++) {
s->zoom_props->applied_zoom_shift[i] += s->zoom_props->zoom_shift[i];
s->zoom_props->applied_zoom_vel_shift[i] +=
s->zoom_props->zoom_vel_shift[i];
s->zoom_props->zoom_shift[i] = 0.0;
s->zoom_props->zoom_vel_shift[i] = 0.0;
}
if (verbose)
message(
"Shifting particles positions by [%f, %f, %f] and "
"velocities by [%f, %f, %f]",
s->zoom_props->applied_zoom_shift[0],
s->zoom_props->applied_zoom_shift[1],
s->zoom_props->applied_zoom_shift[2],
s->zoom_props->applied_zoom_vel_shift[0],
s->zoom_props->applied_zoom_vel_shift[1],
s->zoom_props->applied_zoom_vel_shift[2]);
/* Store the scale factor at which we applied the shift (if we don't yet
* have the engine then we are starting up and will set this in
* engine_init). */
if (s->e != NULL) {
/* Are we doing cosmology? */
if (s->e->policy & engine_policy_cosmology) {
s->zoom_props->scale_factor_at_last_shift = s->e->cosmology->a;
} else {
s->zoom_props->scale_factor_at_last_shift = 1.0;
}
} else {
s->zoom_props->scale_factor_at_last_shift = -1.0;
}
if (verbose) {
message("Applying zoom region shift took %f %s",
clocks_from_ticks(getticks() - tic), clocks_getunit());
}
}
/**
* @brief Compute the void region geometry.
*
* The void region is the region covered by background cells above the zoom
* region. If the void region is sufficiently close to the zoom region size
* then the two will be made equivalent later on. Otherwide the void region is
* equivalent to the buffer region.
*
* This function will derive the bounds of the void region and return how many
* zoom regions tesselate the void region.
*
* @param s The space
* @param ini_max_dim The dim of the zoom region before tesselating the
* volume.
*
* @return The number of zoom regions that tesselate the void region.
*/
int zoom_get_void_geometry(struct space *s, const double region_dim) {
/* Get the lower and upper bounds of the zoom region based on the initial
* dimensions and the padding factor. */
double lower_bounds[3];
double upper_bounds[3];
for (int i = 0; i < 3; i++) {
lower_bounds[i] = (s->dim[i] / 2) - (region_dim / 2.0);
upper_bounds[i] = (s->dim[i] / 2) + (region_dim / 2.0);
}
/* Assign these temporary bounds to the zoom region bounds, we'll overwrite
* these later but useful to have them for now. */
for (int i = 0; i < 3; i++) {
s->zoom_props->region_lower_bounds[i] = lower_bounds[i];
s->zoom_props->region_upper_bounds[i] = upper_bounds[i];
}
/* Find the background cell edges that contain these bounds. */
double void_lower_bounds[3];
double void_upper_bounds[3];
for (int i = 0; i < 3; i++) {
int lower = (int)floor(lower_bounds[i] * s->iwidth[i]);
int upper = (int)floor(upper_bounds[i] * s->iwidth[i]);
void_lower_bounds[i] = lower * s->width[i];
void_upper_bounds[i] = (upper + 1) * s->width[i];
}
/* Assign the void bounds. */
for (int i = 0; i < 3; i++) {
s->zoom_props->void_lower_bounds[i] = void_lower_bounds[i];
s->zoom_props->void_upper_bounds[i] = void_upper_bounds[i];
}
/* Compute the void region dimensions. */
for (int i = 0; i < 3; i++) {
s->zoom_props->void_dim[i] = void_upper_bounds[i] - void_lower_bounds[i];
}
/* Compute the number of zoom regions that tesselate the void region. */
int nr_zoom_regions = ceil(s->zoom_props->void_dim[0] / region_dim);
return nr_zoom_regions;
}
/**
* @brief Compute the number of child cells in a single parent cell at a given
* depth.
*
* @param region_dim The dimension of the region.
* @param parent_width The width of the parent cell.
* @param child_depth The depth of the child cell within the parent.
*
* @return The number of child cells in a single parent cell.
*/
static int zoom_get_cdim_at_depth(double region_dim, double parent_width,
int child_depth) {
/* How many parent_widths are in the region? (ensure correct rounding) */
int region_parent_cdim =
floor((region_dim + (0.1 * parent_width)) / parent_width);
/* We now know how many parent cells we have in the region, use this and the
* depth of the zoom region to calculate the cdim (the number of parents times
* the number of children in a parent. */
return region_parent_cdim * pow(2, child_depth);
}
void zoom_get_geometry_no_buffer_cells(struct space *s) {
/* If we have a buffer cell depth warn that we will ignore it. */
if (s->zoom_props->buffer_cell_depth > 0) {
message("No buffer cells are needed, ignoring buffer cell depth.");
s->zoom_props->buffer_cell_depth = 0;
}
/* Zero the buffer region properties explictly. */
for (int i = 0; i < 3; i++) {
s->zoom_props->buffer_lower_bounds[i] = 0.0;
s->zoom_props->buffer_upper_bounds[i] = 0.0;
s->zoom_props->buffer_dim[i] = 0.0;
s->zoom_props->buffer_cdim[i] = 0;
s->zoom_props->buffer_width[i] = 0.0;
}
/* Match the zoom reigon bounds to the void region bounds. */
for (int i = 0; i < 3; i++) {
s->zoom_props->region_lower_bounds[i] = s->zoom_props->void_lower_bounds[i];
s->zoom_props->region_upper_bounds[i] = s->zoom_props->void_upper_bounds[i];
}
/* Compute the zoom region dimensions. */
for (int i = 0; i < 3; i++) {
s->zoom_props->dim[i] = s->zoom_props->region_upper_bounds[i] -
s->zoom_props->region_lower_bounds[i];
}
/* Compute the number of zoom cells in the void region. */
int cdim = zoom_get_cdim_at_depth(s->zoom_props->dim[0], s->width[0],
s->zoom_props->zoom_cell_depth);
/* Compute the zoom cdim and cell width. */
for (int i = 0; i < 3; i++) {
s->zoom_props->cdim[i] = cdim;
s->zoom_props->width[i] = s->zoom_props->dim[i] / cdim;
s->zoom_props->iwidth[i] = 1.0 / s->zoom_props->width[i];
}
}
/**
* @brief Compute the geometry of the zoom region with buffer cells.
*
* This function computes the geometry of the zoom region when buffer cells are
* enabled. It calculates the bounds, dimensions, and cell widths for both the
* buffer and zoom regions.
*
* Currently, buffer cells are not fully supported and this function will
* simply through an error if called.
*
* @param s The space
*/
void zoom_get_geometry_with_buffer_cells(struct space *s) {
error(
"Buffer cells currently provide no performance benefit and carry a "
"significant complexity cost. They are thus not currently fully "
"supported. Set ZoomRegion:buffer_top_level_depth to 0 to disable "
"buffer cells.");
/* Ensure we have a buffer cell depth. */
if (s->zoom_props->buffer_cell_depth == 0) {
error(
"Current cell structure requires buffer cells but not buffer cell has "
"been given. ZoomRegion:buffer_top_level_depth must be greater than "
"0.");
}
/* Match the buffer region bounds to the void region bounds. */
for (int i = 0; i < 3; i++) {
s->zoom_props->buffer_lower_bounds[i] = s->zoom_props->void_lower_bounds[i];
s->zoom_props->buffer_upper_bounds[i] = s->zoom_props->void_upper_bounds[i];
}
/* Compute the buffer region dimensions. */
for (int i = 0; i < 3; i++) {
s->zoom_props->buffer_dim[i] = s->zoom_props->buffer_upper_bounds[i] -
s->zoom_props->buffer_lower_bounds[i];
}
/* Compute the number of buffer cells in the void region. */
int buffer_cdim =
zoom_get_cdim_at_depth(s->zoom_props->buffer_dim[0], s->width[0],
s->zoom_props->buffer_cell_depth);
/* Compute the buffer cdim and cell width. */
for (int i = 0; i < 3; i++) {
s->zoom_props->buffer_cdim[i] = buffer_cdim;
s->zoom_props->buffer_width[i] = s->zoom_props->buffer_dim[i] / buffer_cdim;
s->zoom_props->buffer_iwidth[i] = 1.0 / s->zoom_props->buffer_width[i];
}
/* Find the buffer cell edges that contain the zoom region bounds. */
double region_lower_bounds[3];
double region_upper_bounds[3];
for (int i = 0; i < 3; i++) {
int lower = (int)floor((s->zoom_props->region_lower_bounds[i] -
s->zoom_props->buffer_lower_bounds[i]) *
s->zoom_props->buffer_iwidth[i]);
int upper = (int)floor((s->zoom_props->region_upper_bounds[i] -
s->zoom_props->buffer_lower_bounds[i]) *
s->zoom_props->buffer_iwidth[i]);
region_lower_bounds[i] = lower * s->zoom_props->buffer_width[i] +
s->zoom_props->buffer_lower_bounds[i];
region_upper_bounds[i] = (upper + 1) * s->zoom_props->buffer_width[i] +
s->zoom_props->buffer_lower_bounds[i];
}
/* Assign the new aligned zoom bounds. */
for (int i = 0; i < 3; i++) {
s->zoom_props->region_lower_bounds[i] = region_lower_bounds[i];
s->zoom_props->region_upper_bounds[i] = region_upper_bounds[i];
}
/* Compute the zoom region dimensions. */
for (int i = 0; i < 3; i++) {
s->zoom_props->dim[i] = region_upper_bounds[i] - region_lower_bounds[i];
}
/* Compute the number of zoom cells in the zoom region. Here we need to
* subtract the buffer depth from the user defined zoom depth, both are
* defined from the background cells but the calculation below is from the
* buffer cells down to the zoom level. */
int cdim = zoom_get_cdim_at_depth(
s->zoom_props->dim[0], s->zoom_props->buffer_width[0],
s->zoom_props->zoom_cell_depth - s->zoom_props->buffer_cell_depth);
/* Compute the zoom cdim and cell width. */
for (int i = 0; i < 3; i++) {
s->zoom_props->cdim[i] = cdim;
s->zoom_props->width[i] = s->zoom_props->dim[i] / cdim;
s->zoom_props->iwidth[i] = 1.0 / s->zoom_props->width[i];
}
}
/**
* @brief Report Zoom Region Properties
*
* This function prints out a table containing the properties of the
* zoom region, if it is enabled. The table includes information such as
* dimensions, center, CDIM, background CDIM, buffer CDIM, region buffer
* ratio, zoom boost factor, minimum zoom cell width, background cell width,
* buffer width, and the number of wanderers.
*
* @param s The space
*/
void zoom_report_cell_properties(const struct space *s) {
struct zoom_region_properties *zoom_props = s->zoom_props;
/* Compute the number of background cells along each side of the void
* region. */
int void_cdim[3];
for (int i = 0; i < 3; i++) {
void_cdim[i] = (int)ceil(s->zoom_props->void_dim[i] * s->iwidth[i]);
}
/* Cdims */
message("%28s = [%d, %d, %d]", "Background cdim", s->cdim[0], s->cdim[1],
s->cdim[2]);
if (zoom_props->with_buffer_cells)
message("%28s = [%d, %d, %d]", "Buffer cdim", zoom_props->buffer_cdim[0],
zoom_props->buffer_cdim[1], zoom_props->buffer_cdim[2]);
message("%28s = [%d, %d, %d]", "Zoom cdim", zoom_props->cdim[0],
zoom_props->cdim[1], zoom_props->cdim[2]);
message("%28s = [%d, %d, %d]", "Void cdim", void_cdim[0], void_cdim[1],
void_cdim[2]);
/* Dimensions */
message("%28s = [%f, %f, %f]", "Background Dimensions", s->dim[0], s->dim[1],
s->dim[2]);
if (zoom_props->with_buffer_cells)
message("%28s = [%f, %f, %f]", "Buffer Region Dimensions",
zoom_props->buffer_dim[0], zoom_props->buffer_dim[1],
zoom_props->buffer_dim[2]);
message("%28s = [%f, %f, %f]", "Zoom Region Dimensions", zoom_props->dim[0],
zoom_props->dim[1], zoom_props->dim[2]);
message("%28s = [%f, %f, %f]", "Void Region Dimensions",
s->zoom_props->void_dim[0], s->zoom_props->void_dim[1],
s->zoom_props->void_dim[2]);
/* Cell Widths */
message("%28s = [%f, %f, %f]", "Background Cell Width", s->width[0],
s->width[1], s->width[2]);
if (zoom_props->with_buffer_cells)
message("%28s = [%f, %f, %f]", "Buffer Cell Width",
zoom_props->buffer_width[0], zoom_props->buffer_width[1],
zoom_props->buffer_width[2]);
message("%28s = [%f, %f, %f]", "Zoom Cell Width", zoom_props->width[0],
zoom_props->width[1], zoom_props->width[2]);
/* Number of Cells */
message("%28s = %d", "Number of Background Cells", zoom_props->nr_bkg_cells);
if (zoom_props->with_buffer_cells)
message("%28s = %d", "Number of Buffer Cells", zoom_props->nr_buffer_cells);
message("%28s = %d", "Number of Zoom Cells", zoom_props->nr_zoom_cells);
/* Bounds */
if (zoom_props->with_buffer_cells)
message(
"%28s = [%f-%f, %f-%f, %f-%f]", "Buffer Bounds",
zoom_props->buffer_lower_bounds[0], zoom_props->buffer_upper_bounds[0],
zoom_props->buffer_lower_bounds[1], zoom_props->buffer_upper_bounds[1],
zoom_props->buffer_lower_bounds[2], zoom_props->buffer_upper_bounds[2]);
message(
"%28s = [%f-%f, %f-%f, %f-%f]", "Zoom Region Bounds",
zoom_props->region_lower_bounds[0], zoom_props->region_upper_bounds[0],
zoom_props->region_lower_bounds[1], zoom_props->region_upper_bounds[1],
zoom_props->region_lower_bounds[2], zoom_props->region_upper_bounds[2]);
/* Depths */
if (zoom_props->with_buffer_cells)
message("%28s = %d", "Buffer Top Level Depth",
zoom_props->buffer_cell_depth);
message("%28s = %d", "Zoom Top Level Depth", zoom_props->zoom_cell_depth);
message("%28s = %d", "Neighbour Max Tree Depth",
zoom_props->neighbour_max_tree_depth);
/* Assorted extra zoom properties */
message("%28s = %f", "Zoom Region Pad Factor", zoom_props->region_pad_factor);
message("%28s = [%f, %f, %f]", "Zoom Region Shift",
zoom_props->applied_zoom_shift[0], zoom_props->applied_zoom_shift[1],
zoom_props->applied_zoom_shift[2]);
message("%28s = [%f, %f, %f]", "Zoom Velocity Shift",
zoom_props->applied_zoom_vel_shift[0],
zoom_props->applied_zoom_vel_shift[1],
zoom_props->applied_zoom_vel_shift[2]);
message("%28s = [%f, %f, %f]", "Zoom Region Center",
zoom_props->region_lower_bounds[0] + (zoom_props->dim[0] / 2.0),
zoom_props->region_lower_bounds[1] + (zoom_props->dim[1] / 2.0),
zoom_props->region_lower_bounds[2] + (zoom_props->dim[2] / 2.0));
}
/**
* @brief Parse and set the zoom region properties.
*
* This function allocates the zoom region properties struct and populates it.
*
* If we're not running a zoom this function will do nothing.
*
* @param params Swift parameter structure.
* @param s The space
* @param verbose Are we talking?
*/
void zoom_props_init(struct swift_params *params, struct space *s,
const int verbose) {
const ticks tic = getticks();
/* If not, we're done here */
if (!s->with_zoom_region) {
return;
}
/* Zoom region properties are stored in a structure. */
s->zoom_props = (struct zoom_region_properties *)malloc(
sizeof(struct zoom_region_properties));
if (s->zoom_props == NULL)
error("Error allocating memory for the zoom parameters.");
bzero(s->zoom_props, sizeof(struct zoom_region_properties));
/* Calculate the gravity mesh distance, we need this for buffer cells and
* neighbour cell labbeling later on. */
/* NOTE: when this is first called we don't have the gravity properties (and
* the engine isn't attached to the space) yet so we need to read directly
* from the params. */
/* Get the mesh size */
int mesh_size = parser_get_param_int(params, "Gravity:mesh_side_length");
/* Calculate the maximum distance at which we have a gravity task based
* on the . */
float a_smooth = parser_get_opt_param_float(params, "Gravity:a_smooth", 1.25);
float r_cut_max_ratio =
parser_get_opt_param_float(params, "Gravity:r_cut_max", 4.5);
float r_s = a_smooth * s->dim[0] / mesh_size;
s->zoom_props->neighbour_distance = r_s * r_cut_max_ratio;
/* Parse the parameter file and populate the properties struct. */
zoom_parse_params(params, s->zoom_props);
if (verbose) {
message("Initialising zoom region properties took %f %s",
clocks_from_ticks(getticks() - tic), clocks_getunit());
}
}
/**
* @brief Initialise the zoom region geometry.
*
* This will compute the cell grid properties ready for cell
* cosntruction when zoom_construct_tl_cells.
*
* @param s The space.
* @param regridding Are we regridding? If so we don't need to compute the
* extent or shift (though we do need to apply the shift to the particles).
* @param verbose Are we talking?
*/
void zoom_region_init(struct space *s, const int regridding,
const int verbose) {
const ticks tic = getticks();
/* Nothing to do if we are restarting, just report geometry and move on. */
if (s->e != NULL && s->e->restarting) {
if (verbose) zoom_report_cell_properties(s);
return;
}
/* Update the neighbour distance in case the gravity props have changed. */
if (s->e != NULL) {
s->zoom_props->neighbour_distance =
s->e->gravity_properties->r_s *
s->e->gravity_properties->r_cut_max_ratio;
}
/* Compute the extent of the zoom region. This also calculates the shift
* necessary to move the zoom region to the centre of the box and stores it in
* s->zoom_props.
* NOTE: If we are regridding this has already been done in zoom_need_regrid
* to check if we need to regrid so we skip it here. */
if (!regridding) zoom_get_region_dim_and_shift(s, verbose);
/* Apply the zoom shift to the particles. */
zoom_apply_zoom_shift_to_particles(s, verbose);
/* The maximal particle extent is the initial dimensions of
* the zoom region. */
const double ini_dim =
max3(s->zoom_props->part_dim[0], s->zoom_props->part_dim[1],
s->zoom_props->part_dim[2]);
/* Include the requested padding around the high resolution particles. */
double max_dim = ini_dim * s->zoom_props->user_region_pad_factor;
/* Define the background grid (we'll treat this as gospel). */
for (int i = 0; i < 3; i++) {
s->cdim[i] = s->zoom_props->bkg_cdim[i];
s->width[i] = s->dim[i] / s->cdim[i];
s->iwidth[i] = 1.0 / s->width[i];
}
/* Compute the void region bounds and number of zoom regions that tesselate
* it. */
int nr_zoom_regions = zoom_get_void_geometry(s, max_dim);
/* Check the user gave a sensible background cdim, if the number of zoom
* regions is too high we will have to set up a unworkable number of buffer
* cells. */
if (nr_zoom_regions >= 64) {
error(
"Background cell size is too large relative to the zoom region! "
"Increase ZoomRegion:bkg_top_level_cells (would have had %d zoom "
"regions in the void region).",
nr_zoom_regions);
}
/* If its alot but not silly just warn the user. */
if (nr_zoom_regions >= 16) {
warning(
"Background cell size is large relative to the zoom region! "
"(would have had %d zoom regions in the void region). ",
nr_zoom_regions);
}
if (verbose) {
message("Initial geometry gives %d zoom regions in the void region.",
nr_zoom_regions);
}
/* Construct the zoom region geometry. */
/* NOTE: here we entirely avoid any buffer cell considerations since they
* provide no performance benefit and are for now vestigual. */
zoom_get_geometry_no_buffer_cells(s);
/* Store what the true boost factor ended up being */
s->zoom_props->region_pad_factor = s->zoom_props->dim[0] / ini_dim;
/* Ensure we haven't got a zoom region smaller than the high resolution
* particle distribution. */
if (s->zoom_props->dim[0] < ini_dim) {
error(
"Found a zoom region smaller than the high resolution particle "
"distribution! Adjust the cell structure "
"(ZoomRegion:bkg_top_level_cells, ZoomRegion:zoom_top_level_cells)");
}
/* Let's be safe and warn if we have drastically changed the size of the
* requested padding region. */
if ((s->zoom_props->region_pad_factor /
s->zoom_props->user_region_pad_factor) >= 2)
warning(
"The pad region has to be %d times larger than requested. "
"Either increase ZoomRegion:region_pad_factor, increase the "
"number of background cells, or increase the depths of the zoom cells.",
(int)(s->zoom_props->region_pad_factor /
s->zoom_props->user_region_pad_factor));
/* If we didn't get an explicit neighbour cell depth we'll match the zoom
* depth. */
s->zoom_props->neighbour_max_tree_depth =
(s->zoom_props->neighbour_max_tree_depth < 0)
? s->zoom_props->zoom_cell_depth
: s->zoom_props->neighbour_max_tree_depth;
/* The neighbour depth must be less the zoom depth or higher if given. */
if (s->zoom_props->neighbour_max_tree_depth <
s->zoom_props->zoom_cell_depth) {
error(
"Zoom neighbour cell depth (%d) must be greater than or equal to the "
"zoom cell depth (%d).",
s->zoom_props->neighbour_max_tree_depth,
s->zoom_props->zoom_cell_depth);
}
/* Set the minimum allowed zoom cell width. */
const double zoom_dmax =
max3(s->zoom_props->dim[0], s->zoom_props->dim[1], s->zoom_props->dim[2]);
s->zoom_props->cell_min = 0.99 * zoom_dmax / s->zoom_props->cdim[0];
/* Set the minimum background cell size. */
const double dmax = max3(s->dim[0], s->dim[1], s->dim[2]);
s->cell_min = 0.99 * dmax / s->cdim[0];
/* Store cell numbers and offsets. */
s->zoom_props->bkg_cell_offset =
s->zoom_props->cdim[0] * s->zoom_props->cdim[1] * s->zoom_props->cdim[2];
s->zoom_props->nr_zoom_cells = s->zoom_props->bkg_cell_offset;
s->zoom_props->nr_bkg_cells = s->cdim[0] * s->cdim[1] * s->cdim[2];
s->zoom_props->buffer_cell_offset =
s->zoom_props->bkg_cell_offset + s->zoom_props->nr_bkg_cells;
s->zoom_props->nr_buffer_cells = s->zoom_props->buffer_cdim[0] *
s->zoom_props->buffer_cdim[1] *
s->zoom_props->buffer_cdim[2];
/* Report what we have done */
if (verbose) {
zoom_report_cell_properties(s);
}
/* Report how long it took. */
if (verbose) {
message("Zoom region initialisation took %f %s",
clocks_from_ticks(getticks() - tic), clocks_getunit());
}
}