/******************************************************************************* * This file is part of SWIFT. * Copyright (C) 2026. * * 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 . * ******************************************************************************/ #include /* Some standard headers. */ #include #include /* Local headers. */ #include "swift.h" /** * @brief Set up a cell with a singular particle. * * @param c The #cell pointer to set up. * @param loc The location of the cell. * @param width The width of the cell. * @param x The position of the particle in the cell. */ static void setup_cell(struct cell *c, const double loc[3], const double width[3], const double x[3]) { bzero(c, sizeof(struct cell)); c->loc[0] = loc[0]; c->loc[1] = loc[1]; c->loc[2] = loc[2]; c->width[0] = width[0]; c->width[1] = width[1]; c->width[2] = width[2]; c->grav.count = 1; c->grav.multipole = (struct gravity_tensors *)malloc(sizeof(struct gravity_tensors)); if (c->grav.multipole == NULL) error("Failed to allocate multipole"); bzero(c->grav.multipole, sizeof(struct gravity_tensors)); if (posix_memalign((void **)&c->grav.parts, gpart_align, c->grav.count * sizeof(struct gpart)) != 0) error("Error allocating gparts"); bzero(c->grav.parts, c->grav.count * sizeof(struct gpart)); c->grav.parts[0].x[0] = x[0]; c->grav.parts[0].x[1] = x[1]; c->grav.parts[0].x[2] = x[2]; c->grav.parts[0].mass = 1.0; c->grav.parts[0].time_bin = 1; c->grav.parts[0].type = swift_type_dark_matter; } /** * @brief Free the memory allocated for a cell. * * @param c The #cell pointer to free. */ static void free_cell(struct cell *c) { free(c->grav.multipole); free(c->grav.parts); } /** * @brief Test the mesh rebuild criterion checks. * * This test builds two leaf cells with one particle each, builds their * multipoles, and checks that the mesh criterion is satisfied at rebuild and * every value is as expected. It then drifts the multipoles to accumulate * to modify the dx_max variables used in the mesh criterion and checks that * the criterion is correctly flagged after the drift. Finally, it rebuilds the * multipoles again to check that the dx_max variables are cleared at rebuild * for the next criterion check. */ int main(int argc, char *argv[]) { (void)argc; (void)argv; /* Set up an engine with a periodic space, a PM mesh, and grav properties. */ struct engine e; struct space s; struct pm_mesh mesh; struct gravity_props props; bzero(&e, sizeof(struct engine)); bzero(&s, sizeof(struct space)); bzero(&mesh, sizeof(struct pm_mesh)); bzero(&props, sizeof(struct gravity_props)); s.periodic = 1; s.dim[0] = 10.0; s.dim[1] = 10.0; s.dim[2] = 10.0; mesh.periodic = 1; mesh.dim[0] = s.dim[0]; mesh.dim[1] = s.dim[1]; mesh.dim[2] = s.dim[2]; mesh.r_cut_min = 0.0; mesh.r_cut_max = 2.5; mesh.r_s = 1.0; mesh.r_s_inv = 1.0; props.epsilon_DM_cur = 0.1f; props.epsilon_baryon_cur = 0.1f; e.s = &s; e.mesh = &mesh; e.gravity_properties = &props; /* The cell pair to check. */ struct cell ci; struct cell cj; const double width[3] = {1.0, 1.0, 1.0}; const double loc_i[3] = {0.0, 0.0, 0.0}; const double loc_j[3] = {3.4, 0.0, 0.0}; const double x_i[3] = {0.5, 0.5, 0.5}; const double x_j[3] = {3.5, 0.5, 0.5}; /* Build two leaf cells with one particle each for mesh criterion checks. */ setup_cell(&ci, loc_i, width, x_i); setup_cell(&cj, loc_j, width, x_j); /* Seed dx_max with junk to ensure rebuild zeroes it. */ ci.grav.multipole->dx_max[0] = 1.0f; ci.grav.multipole->dx_max[1] = 2.0f; ci.grav.multipole->dx_max[2] = 3.0f; cj.grav.multipole->dx_max[0] = 4.0f; cj.grav.multipole->dx_max[1] = 5.0f; cj.grav.multipole->dx_max[2] = 6.0f; /* Rebuild multipoles as done during space_rebuild (but before mesh criterion * checks.) */ cell_make_multipoles(&ci, /*ti_current=*/0, &props); cell_make_multipoles(&cj, /*ti_current=*/0, &props); /* Confirm the multipoles were built. */ if (ci.grav.multipole->m_pole.M_000 <= 0.) error("ci multipole mass not set"); if (cj.grav.multipole->m_pole.M_000 <= 0.) error("cj multipole mass not set"); /* Check that the dx_max variables were cleared at rebuild. */ for (int k = 0; k < 3; ++k) { if (ci.grav.multipole->dx_max[k] != 0.f) error("dx_max not zeroed for ci: %e", ci.grav.multipole->dx_max[k]); if (cj.grav.multipole->dx_max[k] != 0.f) error("dx_max not zeroed for cj: %e", cj.grav.multipole->dx_max[k]); } /* At rebuild, the minimal distance with dx_max should be below the mesh * cutoff so the mesh criterion is satisfied. */ const double max_distance2 = mesh.r_cut_max * mesh.r_cut_max; const double min_radius2_rebuild = cell_min_dist2_with_max_dx(&ci, &cj, s.periodic, s.dim); if (min_radius2_rebuild >= max_distance2) error( "Cells should be able to use the mesh at rebuild: min_radius2=%e " "max_distance2=%e", min_radius2_rebuild, max_distance2); /* Drift the multipoles to accumulate dx_max and re-evaluate the criterion. */ ci.grav.multipole->m_pole.vel[0] = 0.1f; ci.grav.multipole->m_pole.max_delta_vel[0] = 0.2f; ci.grav.multipole->m_pole.min_delta_vel[0] = -0.2f; cj.grav.multipole->m_pole.vel[1] = -0.1f; cj.grav.multipole->m_pole.max_delta_vel[1] = 0.3f; cj.grav.multipole->m_pole.min_delta_vel[1] = -0.3f; /* Drift the multipoles to accumulate dx_max. */ gravity_drift(ci.grav.multipole, 1.0); gravity_drift(cj.grav.multipole, 2.0); /* Check that the dx_max variables were updated by the drift. */ if (ci.grav.multipole->dx_max[0] <= 0.f) error("dx_max did not increase after drift for ci"); if (cj.grav.multipole->dx_max[1] <= 0.f) error("dx_max did not increase after drift for cj"); /* After drift, the minimal distance with dx_max should exceed the mesh * cutoff so the mesh criterion is no longer satisfied. */ const double min_radius2_drift = cell_min_dist2_with_max_dx(&ci, &cj, s.periodic, s.dim); if (min_radius2_drift <= max_distance2) error( "dx_max large enough to need pair task but mesh rebuild not flagged: " "min_radius2=%e max_distance2=%e", min_radius2_drift, max_distance2); /* Rebuild again and ensure the dx_max variables are cleared. */ cell_make_multipoles(&ci, /*ti_current=*/1, &props); cell_make_multipoles(&cj, /*ti_current=*/1, &props); for (int k = 0; k < 3; ++k) { if (ci.grav.multipole->dx_max[k] != 0.f) error("dx_max not zeroed after rebuild for ci: %e", ci.grav.multipole->dx_max[k]); if (cj.grav.multipole->dx_max[k] != 0.f) error("dx_max not zeroed after rebuild for cj: %e", cj.grav.multipole->dx_max[k]); } #ifdef SWIFT_DEBUG_CHECKS /* Sanity-check the rebuild invariants. */ cell_check_multipole(&ci, &props); cell_check_multipole(&cj, &props); #endif free_cell(&ci); free_cell(&cj); return 0; }