/*******************************************************************************
* 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)
* 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 parameters. */
#include
/* Local headers. */
#include "cell.h"
#include "engine.h"
#include "gravity_properties.h"
#include "scheduler.h"
#include "space.h"
#include "zoom.h"
/**
* @brief Check whether we need a regrid based on the zoom region motion.
*
* If the zoom region has moved more than a certain fraction of its size
* in any dimension then we need to regrid. This is to ensure that the
* zoom region remains well centred on the particle distribution.
*
* @param s The #space.
* @return 1 if a zoom regrid is needed, 0 otherwise.
*/
static int zoom_need_regrid_motion(const struct space *s) {
const double dx[3] = {s->zoom_props->zoom_shift[0],
s->zoom_props->zoom_shift[1],
s->zoom_props->zoom_shift[2]};
const double max_shift = s->zoom_props->max_com_dx;
if (dx[0] > max_shift * s->zoom_props->dim[0] ||
dx[1] > max_shift * s->zoom_props->dim[1] ||
dx[2] > max_shift * s->zoom_props->dim[2]) {
message(
"Zoom region shift exceeds %.2f%% of the zoom region in at least one "
"dimension (shift=(%.3e,%.3e,%.3e), zoom_dim=(%.3e,%.3e,%.3e)).",
max_shift * 100.0, dx[0], dx[1], dx[2], s->zoom_props->dim[0],
s->zoom_props->dim[1], s->zoom_props->dim[2]);
return 1;
}
return 0;
}
/**
* @brief Check whether we need a regrid based on the particle extent.
*
* If the particle distribution exceeds a certain fraction of the zoom region
* size in any dimension then we need to regrid. This is to ensure that the
* zoom region remains well centred on the particle distribution.
*
* @param s The #space.
* @return 1 if a zoom regrid is needed, 0 otherwise.
*/
static int zoom_need_regrid_extent(const struct space *s) {
/* Derive the maximum allowed particle extent from the user specified
* target padding fraction, if the particle distribution is more than
* this fraction then we are no longer doing what the user asked for. */
const double max_part_dim_frac = 1.0 / s->zoom_props->user_region_pad_factor;
/* Get the maximum allowed extent of the particle distribution. */
const double max_dim[3] = {s->zoom_props->dim[0] * max_part_dim_frac,
s->zoom_props->dim[1] * max_part_dim_frac,
s->zoom_props->dim[2] * max_part_dim_frac};
/* If the particle distribution is more than 90% of the zoom region width
* (based on the zoom cells themselves) in any dimension we need to regrid. */
if (s->zoom_props->part_dim[0] > max_dim[0] ||
s->zoom_props->part_dim[1] > max_dim[1] ||
s->zoom_props->part_dim[2] > max_dim[2]) {
message(
"Particle distribution exceeds %.2f%% of the zoom region "
"in at least one dimension (part_dim=(%.3e,%.3e,%.3e), "
"zoom_dim=(%.3e,%.3e,%.3e)).",
max_part_dim_frac * 100.0, s->zoom_props->part_dim[0],
s->zoom_props->part_dim[1], s->zoom_props->part_dim[2],
s->zoom_props->dim[0], s->zoom_props->dim[1], s->zoom_props->dim[2]);
return 1;
}
return 0;
}
/**
* @brief Check whether we need a regrid based on hmax requiring larger cells.
*
* If the current hmax requires larger cells in the zoom region than we
* currently have then we need to regrid.
*
* @param s The #space.
* @param new_cdim The new top-level cell dimensions (based on current hmax).
* @return 1 if a zoom regrid is needed, 0 otherwise.
*/
static int zoom_need_regrid_hmax(const struct space *s, const int new_cdim[3]) {
return (new_cdim[0] < s->zoom_props->cdim[0] ||
new_cdim[1] < s->zoom_props->cdim[1] ||
new_cdim[2] < s->zoom_props->cdim[2]);
}
/**
* @brief Check whether we need a regrid based on the zoom region.
*
* This function is called in space_regrid in space_regrid.c and provides
* the zoom specific regrid check.
*
* Unlike a uniform box, in a zoom simulation there are three reasons we might
* need to regrid:
* 1) The high resolution particle distribution has moved too far from the
* centre of the zoom region.
* 2) The high resolution particle distribution has expanded too far and is
* likely to be no longer well contained within the zoom region soon.
* 3) [Same as a uniform box] The maximum smoothing length (hmax) has increased
* such that the current zoom cells are too small to properly resolve the
* hydrodynamics.
*
* @param s The #space.
* @param new_cdim The new top-level cell dimensions (based on current hmax).
*
* @return 1 if a zoom regrid is needed, 0 otherwise.
*/
int zoom_need_regrid(const struct space *s, const int new_cdim[3]) {
/* Have we exceeded the allowed shift of the zoom region? */
if (zoom_need_regrid_motion(s)) {
return 1;
}
/* Has the particle distribution exceeded the allowed extent? */
if (zoom_need_regrid_extent(s)) {
return 1;
}
/* Has hmax increased such that we need larger zoom cells? */
if (zoom_need_regrid_hmax(s, new_cdim)) {
return 1;
}
return 0;
}
/**
* @brief Find an acceptable geometry given the required zoom cdim.
*
* This function has multiple use cases:
* 1) We are regridding due to the zoom region motion. In this case the
* particle distribution itself needs shifting and the cells need to be
* rebuilt around it.
* 2) We are regridding due to the zoom region extent. In this case the
* particle distribution itself needs shifting and the cells need to be
* rebuilt around expanding the width of the zoom region.
* 3) We are regridding due to hmax requiring larger cells in the zoom region.
* In this case we need to find a new geometry that can accommodate the new
* cdim (note that the particles will be shifted in this case too).
*
* If we are doing the first case we simply call zoom_region_init to shift and
* recalculate the geometry and we're done. The second and third cases are more
* complex and require changes to the cell dimensions.
*
* For the second and third cases we'll first try to decrease the background
* cdim a reasonable amount since this will carry less of a performance penalty
* than doubling the size of zoom cells. This is also most likely to produce a
* valid set up in all but the most extreme cases.
*
* @param s The #space.
* @param new_cdim The new top-level cell dimensions (based on current hmax).
*/
void zoom_regrid_find_acceptable_geometry(struct space *s,
const int new_cdim[3]) {
/* If we do not need a regrid just exit. */
if (!zoom_need_regrid(s, new_cdim)) {
return;
}
/* If we are regridding due to motion then just try to recentre the zoom
* region. */
if (zoom_need_regrid_motion(s) && !zoom_need_regrid_extent(s) &&
!zoom_need_regrid_hmax(s, new_cdim)) {
/* Recalculate the zoom region geometry. */
zoom_region_init(s, /*regridding=*/1, /*verbose=*/1);
return;
}
/* Otherwise, we need to also adjust input cell geometry. */
/* Loop until we've found an acceptable geometry. */
int old_bkg_cdim = s->cdim[0];
while (zoom_need_regrid(s, new_cdim)) {
/* First try decreasing the background cdim to a minimum of 50% its
* current value. */
while (zoom_need_regrid(s, new_cdim) && s->cdim[0] > 0.5 * old_bkg_cdim) {
message(
"Adjusting background cell size to find acceptable zoom geometry ("
"bkg_cdim=(%d,%d,%d)).",
s->zoom_props->bkg_cdim[0] - 1, s->zoom_props->bkg_cdim[1] - 1,
s->zoom_props->bkg_cdim[2] - 1);
/* Decrement the background cdim. */
s->zoom_props->bkg_cdim[0]--;
s->zoom_props->bkg_cdim[1]--;
s->zoom_props->bkg_cdim[2]--;
/* Recalculate the zoom region geometry. */
zoom_region_init(s, /*regridding=*/1, /*verbose=*/1);
}
/* If this worked we can stop here. */
if (!zoom_need_regrid(s, new_cdim)) {
break;
}
/* If we aren't doing a hmax based regrid we have now failed, adjusting
* the zoom region depth won't help us. */
if (!zoom_need_regrid_hmax(s, new_cdim)) {
error(
"Failed to find an acceptable zoom region before reaching 50%% of "
"the initial background cell size (i.e. not a hmax based regrid). "
"Try to decrease the initial background cell size.");
}
/* Reset the background cdim to its original value, we'll try decreasing
* the zoom region depth instead, and then loop again. */
s->zoom_props->bkg_cdim[0] = old_bkg_cdim;
s->zoom_props->bkg_cdim[1] = old_bkg_cdim;
s->zoom_props->bkg_cdim[2] = old_bkg_cdim;
message(
"Adjusting zoom cell size to find acceptable zoom geometry "
"(depth=%d).",
s->zoom_props->zoom_cell_depth - 1);
/* If adjusting the background didn't work then decrease the zoom region
* depth by one (doubling the zoom cell size). */
s->zoom_props->zoom_cell_depth--;
/* Ensure we don't go below depth 1. */
if (s->zoom_props->zoom_cell_depth < 1) {
error(
"Failed to find an acceptable zoom region before reaching depth=0 "
"(i.e. not a zoom). Try to decrease the initial background cell "
"size.");
}
/* Recalculate the zoom region geometry. */
zoom_region_init(s, /*regridding=*/1, /*verbose=*/1);
}
}
/**
* @brief Prepare the cells for the zoom region.
*
* This function is called in space_regrid in space_regrid.c and provides
* the zoom specific cell preparation, freeing only the zoom specific cell
* pointer arrays and computing the new zoom region geometry if necessary.
*
* The cells are counted here and stored on the space. In the zoom case this
* includes counts from each top-level cell grid (zoom, bkg, and buffer if
* used).
*
* @param s The #space.
* @param zoom_cdim The new top-level cell dimensions (based on current hmax).
* @param verbose Whether to print verbose output.
*/
void zoom_prepare_cells(struct space *s, const int zoom_cdim[3], int verbose) {
/* Free the old zoom specific cells, if they were allocated. */
if (s->cells_top != NULL) {
swift_free("local_zoom_cells_top", s->zoom_props->local_zoom_cells_top);
swift_free("local_bkg_cells_top", s->zoom_props->local_bkg_cells_top);
swift_free("local_zoom_cells_with_particles_top",
s->zoom_props->local_zoom_cells_with_particles_top);
swift_free("local_bkg_cell_with_particless_top",
s->zoom_props->local_bkg_cells_with_particles_top);
swift_free("local_buffer_cell_with_particless_top",
s->zoom_props->local_buffer_cells_with_particles_top);
swift_free("void_cell_indices", s->zoom_props->void_cell_indices);
swift_free("neighbour_cells_top", s->zoom_props->neighbour_cells_top);
/* Find an acceptable geometry given the required zoom cdim. */
zoom_regrid_find_acceptable_geometry(s, zoom_cdim);
/* The above function found the geometry silently, if we're running in
* verbose mode print the cell properties report. */
if (verbose) {
zoom_report_cell_properties(s);
}
}
/* Count the number of top level cells. */
s->tot_cells = s->nr_cells =
(s->cdim[0] * s->cdim[1] * s->cdim[2]) +
(s->zoom_props->cdim[0] * s->zoom_props->cdim[1] *
s->zoom_props->cdim[2]) +
(s->zoom_props->buffer_cdim[0] * s->zoom_props->buffer_cdim[1] *
s->zoom_props->buffer_cdim[2]);
}
/**
* @brief Allocate the cell indices arrays used for the zoom region.
*
* @param s The #space.
*/
void zoom_allocate_cells(struct space *s) {
/* Allocate the indices of local zoom cells */
if (swift_memalign("local_zoom_cells_top",
(void **)&s->zoom_props->local_zoom_cells_top,
SWIFT_STRUCT_ALIGNMENT,
s->zoom_props->nr_zoom_cells * sizeof(int)) != 0)
error("Failed to allocate indices of local top-level zoom cells.");
bzero(s->zoom_props->local_zoom_cells_top,
s->zoom_props->nr_zoom_cells * sizeof(int));
/* Allocate the indices of local bkg cells */
if (swift_memalign("local_bkg_cells_top",
(void **)&s->zoom_props->local_bkg_cells_top,
SWIFT_STRUCT_ALIGNMENT,
s->zoom_props->nr_bkg_cells * sizeof(int)) != 0)
error("Failed to allocate indices of local top-level background cells.");
bzero(s->zoom_props->local_bkg_cells_top,
s->zoom_props->nr_bkg_cells * sizeof(int));
/* Allocate the indices of local buffer cells */
if (s->zoom_props->with_buffer_cells) {
if (swift_memalign("local_buffer_cells_top",
(void **)&s->zoom_props->local_buffer_cells_top,
SWIFT_STRUCT_ALIGNMENT,
s->zoom_props->nr_buffer_cells * sizeof(int)) != 0)
error("Failed to allocate indices of local top-level background cells.");
bzero(s->zoom_props->local_buffer_cells_top,
s->zoom_props->nr_buffer_cells * sizeof(int));
}
/* Allocate the indices of local zoom cells with particles */
if (swift_memalign(
"local_zoom_cells_with_particles_top",
(void **)&s->zoom_props->local_zoom_cells_with_particles_top,
SWIFT_STRUCT_ALIGNMENT,
s->zoom_props->nr_zoom_cells * sizeof(int)) != 0)
error(
"Failed to allocate indices of local top-level zoom cells with "
"particles.");
bzero(s->zoom_props->local_zoom_cells_with_particles_top,
s->zoom_props->nr_zoom_cells * sizeof(int));
/* Allocate the indices of local bkg cells with particles */
if (swift_memalign(
"local_bkg_cells_with_particles_top",
(void **)&s->zoom_props->local_bkg_cells_with_particles_top,
SWIFT_STRUCT_ALIGNMENT,
s->zoom_props->nr_bkg_cells * sizeof(int)) != 0)
error(
"Failed to allocate indices of local top-level background cells with "
"particles.");
bzero(s->zoom_props->local_bkg_cells_with_particles_top,
s->zoom_props->nr_bkg_cells * sizeof(int));
/* Allocate the indices of local buffer cells with particles */
if (s->zoom_props->with_buffer_cells) {
if (swift_memalign(
"local_buffer_cells_with_particles_top",
(void **)&s->zoom_props->local_buffer_cells_with_particles_top,
SWIFT_STRUCT_ALIGNMENT,
s->zoom_props->nr_buffer_cells * sizeof(int)) != 0)
error(
"Failed to allocate indices of local top-level buffer cells with "
"particles.");
bzero(s->zoom_props->local_buffer_cells_with_particles_top,
s->zoom_props->nr_buffer_cells * sizeof(int));
}
}