/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2017 Matthieu Schaller (schaller@strw.leidenuniv.nl)
*
* 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
/* Some standard headers. */
#include
#include
#include
#include
#ifdef HAVE_HDF5
#include
#endif
/* This object's header. */
#include "gravity.h"
/* Local headers. */
#include "active.h"
#include "error.h"
#include "kernel_gravity.h"
#include "kernel_long_gravity.h"
#include "threadpool.h"
#include "version.h"
struct exact_force_data {
const struct engine *e;
const struct space *s;
int counter_global;
double const_G;
};
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
/* Size of the Ewald table */
#define Newald 64
/* Components of the Ewald correction */
static float fewald_x[Newald + 1][Newald + 1][Newald + 1];
static float fewald_y[Newald + 1][Newald + 1][Newald + 1];
static float fewald_z[Newald + 1][Newald + 1][Newald + 1];
static float potewald[Newald + 1][Newald + 1][Newald + 1];
/* Factor used to normalize the access to the Ewald table */
float ewald_fac;
#endif
/**
* @brief Allocates the memory and computes one octant of the
* Ewald correction table.
*
* We follow Hernquist, Bouchet & Suto, 1991, ApJS, Volume 75, p.231-240,
* equations (2.14a) and (2.14b) with alpha = 2. We consider all terms with
* |x - nL| < 4L and |h|^2 < 16.
*
* @param boxSize The side-length (L) of the volume.
*/
void gravity_exact_force_ewald_init(const double boxSize) {
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
const float boxSize_inv = 1.f / boxSize;
const float boxSize_inv2 = 1.f / (boxSize * boxSize);
int use_file = 0;
#ifdef HAVE_HDF5
if (access("Ewald.hdf5", R_OK) != -1) use_file = 1;
#endif
/* Can we use the stored HDF5 file? */
if (use_file) {
const ticks tic = getticks();
message("Reading Ewald correction table from file...");
#ifdef HAVE_HDF5
const hid_t h_file = H5Fopen("Ewald.hdf5", H5F_ACC_RDONLY, H5P_DEFAULT);
if (h_file < 0) error("Error opening the old 'Ewald.hdf5' file.");
/* Check the table size */
const hid_t h_grp = H5Gopen1(h_file, "Info");
const hid_t h_attr = H5Aopen(h_grp, "Ewald_size", H5P_DEFAULT);
int size;
H5Aread(h_attr, H5T_NATIVE_INT, &size);
if (size != Newald)
error("File 'Ewald.hdf5' contains arrays of the wrong size");
H5Aclose(h_attr);
H5Gclose(h_grp);
/* Now read the tables themselves */
hid_t h_data;
h_data = H5Dopen(h_file, "Ewald_x", H5P_DEFAULT);
H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
&(fewald_x[0][0][0]));
H5Dclose(h_data);
h_data = H5Dopen(h_file, "Ewald_y", H5P_DEFAULT);
H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
&(fewald_y[0][0][0]));
H5Dclose(h_data);
h_data = H5Dopen(h_file, "Ewald_z", H5P_DEFAULT);
H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
&(fewald_z[0][0][0]));
H5Dclose(h_data);
h_data = H5Dopen(h_file, "Ewald_pot", H5P_DEFAULT);
H5Dread(h_data, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT,
&(potewald[0][0][0]));
H5Dclose(h_data);
/* Done */
H5Fclose(h_file);
#endif
/* Report time this took */
message("Ewald correction table read in (took %.3f %s). ",
clocks_from_ticks(getticks() - tic), clocks_getunit());
} else {
/* Ok.. let's recompute everything */
const ticks tic = getticks();
message("Computing Ewald correction table...");
/* Level of correction (Hernquist et al. 1991)*/
const float alpha = 2.f;
/* some useful constants */
const float alpha2 = alpha * alpha;
const float factor_exp1 = 2.f * alpha / sqrt(M_PI);
const float factor_exp2 = -M_PI * M_PI / alpha2;
const float factor_sin = 2.f * M_PI;
const float factor_cos = 2.f * M_PI;
const float factor_pot = M_PI / alpha2;
/* Zero everything */
bzero(fewald_x, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
bzero(fewald_y, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
bzero(fewald_z, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
bzero(potewald, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float));
/* Hernquist, Bouchet & Suto, 1991, Eq. 2.10 and just below Eq. 2.15 */
potewald[0][0][0] = 2.8372975f;
/* Compute the values in one of the octants */
for (int i = 0; i <= Newald; ++i) {
for (int j = 0; j <= Newald; ++j) {
for (int k = 0; k <= Newald; ++k) {
if (i == 0 && j == 0 && k == 0) continue;
/* Distance vector */
const float r_x = 0.5f * ((float)i) / Newald;
const float r_y = 0.5f * ((float)j) / Newald;
const float r_z = 0.5f * ((float)k) / Newald;
/* Norm of distance vector */
const float r2 = r_x * r_x + r_y * r_y + r_z * r_z;
const float r_inv = 1.f / sqrtf(r2);
const float r_inv3 = r_inv * r_inv * r_inv;
/* Normal gravity potential term */
float f_x = r_x * r_inv3;
float f_y = r_y * r_inv3;
float f_z = r_z * r_inv3;
float pot = r_inv + factor_pot;
for (int n_i = -4; n_i <= 4; ++n_i) {
for (int n_j = -4; n_j <= 4; ++n_j) {
for (int n_k = -4; n_k <= 4; ++n_k) {
const float d_x = r_x - n_i;
const float d_y = r_y - n_j;
const float d_z = r_z - n_k;
/* Discretised distance */
const float r_tilde2 = d_x * d_x + d_y * d_y + d_z * d_z;
const float r_tilde_inv = 1.f / sqrtf(r_tilde2);
const float r_tilde = r_tilde_inv * r_tilde2;
const float r_tilde_inv3 =
r_tilde_inv * r_tilde_inv * r_tilde_inv;
const float val_pot = erfcf(alpha * r_tilde);
const float val_f =
val_pot + factor_exp1 * r_tilde * expf(-alpha2 * r_tilde2);
/* First correction term */
const float f = val_f * r_tilde_inv3;
f_x -= f * d_x;
f_y -= f * d_y;
f_z -= f * d_z;
pot -= val_pot * r_tilde_inv;
}
}
}
for (int h_i = -4; h_i <= 4; ++h_i) {
for (int h_j = -4; h_j <= 4; ++h_j) {
for (int h_k = -4; h_k <= 4; ++h_k) {
const float h2 = h_i * h_i + h_j * h_j + h_k * h_k;
if (h2 == 0.f) continue;
const float h2_inv = 1.f / (h2 + FLT_MIN);
const float h_dot_x = h_i * r_x + h_j * r_y + h_k * r_z;
const float common = h2_inv * expf(h2 * factor_exp2);
const float val_pot =
(float)M_1_PI * common * cosf(factor_cos * h_dot_x);
const float val_f = 2.f * common * sinf(factor_sin * h_dot_x);
/* Second correction term */
f_x -= val_f * h_i;
f_y -= val_f * h_j;
f_z -= val_f * h_k;
pot -= val_pot;
}
}
}
/* Save back to memory */
fewald_x[i][j][k] = f_x;
fewald_y[i][j][k] = f_y;
fewald_z[i][j][k] = f_z;
potewald[i][j][k] = pot;
}
}
}
/* Dump the Ewald table to a file */
#ifdef HAVE_HDF5
hid_t h_file =
H5Fcreate("Ewald.hdf5", H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
if (h_file < 0) error("Error while opening file for Ewald dump.");
/* Write the Ewald table size */
const int size = Newald;
const hid_t h_grp = H5Gcreate1(h_file, "Info", 0);
const hid_t h_aspace = H5Screate(H5S_SCALAR);
hid_t h_att =
H5Acreate1(h_grp, "Ewald_size", H5T_NATIVE_INT, h_aspace, H5P_DEFAULT);
H5Awrite(h_att, H5T_NATIVE_INT, &size);
H5Aclose(h_att);
H5Gclose(h_grp);
H5Sclose(h_aspace);
/* Create dataspace and write arrays */
hsize_t dim[3] = {Newald + 1, Newald + 1, Newald + 1};
hid_t h_space = H5Screate_simple(3, dim, NULL);
hid_t h_data;
h_data = H5Dcreate(h_file, "Ewald_x", H5T_NATIVE_FLOAT, h_space,
H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
&(fewald_x[0][0][0]));
H5Dclose(h_data);
h_data = H5Dcreate(h_file, "Ewald_y", H5T_NATIVE_FLOAT, h_space,
H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
&(fewald_y[0][0][0]));
H5Dclose(h_data);
h_data = H5Dcreate(h_file, "Ewald_z", H5T_NATIVE_FLOAT, h_space,
H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
&(fewald_z[0][0][0]));
H5Dclose(h_data);
h_data = H5Dcreate(h_file, "Ewald_pot", H5T_NATIVE_FLOAT, h_space,
H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT,
&(potewald[0][0][0]));
H5Dclose(h_data);
H5Sclose(h_space);
H5Fclose(h_file);
#endif
/* Report time this took */
message("Ewald correction table computed (took %.3f %s). ",
clocks_from_ticks(getticks() - tic), clocks_getunit());
}
/* Ewald factor to access the table */
ewald_fac = (double)(2 * Newald) / boxSize;
/* Apply the box-size correction */
for (int i = 0; i <= Newald; ++i) {
for (int j = 0; j <= Newald; ++j) {
for (int k = 0; k <= Newald; ++k) {
fewald_x[i][j][k] *= boxSize_inv2;
fewald_y[i][j][k] *= boxSize_inv2;
fewald_z[i][j][k] *= boxSize_inv2;
potewald[i][j][k] *= boxSize_inv;
}
}
}
#else
error("Gravity checking function called without the corresponding flag.");
#endif
}
/**
* @brief Compute the Ewald correction for a given distance vector r.
*
* We interpolate the Ewald correction tables using a tri-linear interpolation
* similar to a CIC.
*
* @param rx x-coordinate of distance vector.
* @param ry y-coordinate of distance vector.
* @param rz z-coordinate of distance vector.
* @param corr_f (return) The Ewald correction for the force.
* @param corr_p (return) The Ewald correction for the potential.
*/
void gravity_exact_force_ewald_evaluate(double rx, double ry, double rz,
double corr_f[3], double *corr_p) {
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
const double s_x = (rx < 0.) ? 1. : -1.;
const double s_y = (ry < 0.) ? 1. : -1.;
const double s_z = (rz < 0.) ? 1. : -1.;
rx = fabs(rx);
ry = fabs(ry);
rz = fabs(rz);
int i = (int)(rx * ewald_fac);
if (i >= Newald) i = Newald - 1;
const double dx = rx * ewald_fac - i;
const double tx = 1. - dx;
int j = (int)(ry * ewald_fac);
if (j >= Newald) j = Newald - 1;
const double dy = ry * ewald_fac - j;
const double ty = 1. - dy;
int k = (int)(rz * ewald_fac);
if (k >= Newald) k = Newald - 1;
const double dz = rz * ewald_fac - k;
const double tz = 1. - dz;
/* Interpolation in X */
corr_f[0] = 0.;
corr_f[0] += fewald_x[i + 0][j + 0][k + 0] * tx * ty * tz;
corr_f[0] += fewald_x[i + 0][j + 0][k + 1] * tx * ty * dz;
corr_f[0] += fewald_x[i + 0][j + 1][k + 0] * tx * dy * tz;
corr_f[0] += fewald_x[i + 0][j + 1][k + 1] * tx * dy * dz;
corr_f[0] += fewald_x[i + 1][j + 0][k + 0] * dx * ty * tz;
corr_f[0] += fewald_x[i + 1][j + 0][k + 1] * dx * ty * dz;
corr_f[0] += fewald_x[i + 1][j + 1][k + 0] * dx * dy * tz;
corr_f[0] += fewald_x[i + 1][j + 1][k + 1] * dx * dy * dz;
corr_f[0] *= s_x;
/* Interpolation in Y */
corr_f[1] = 0.;
corr_f[1] += fewald_y[i + 0][j + 0][k + 0] * tx * ty * tz;
corr_f[1] += fewald_y[i + 0][j + 0][k + 1] * tx * ty * dz;
corr_f[1] += fewald_y[i + 0][j + 1][k + 0] * tx * dy * tz;
corr_f[1] += fewald_y[i + 0][j + 1][k + 1] * tx * dy * dz;
corr_f[1] += fewald_y[i + 1][j + 0][k + 0] * dx * ty * tz;
corr_f[1] += fewald_y[i + 1][j + 0][k + 1] * dx * ty * dz;
corr_f[1] += fewald_y[i + 1][j + 1][k + 0] * dx * dy * tz;
corr_f[1] += fewald_y[i + 1][j + 1][k + 1] * dx * dy * dz;
corr_f[1] *= s_y;
/* Interpolation in Z */
corr_f[2] = 0.;
corr_f[2] += fewald_z[i + 0][j + 0][k + 0] * tx * ty * tz;
corr_f[2] += fewald_z[i + 0][j + 0][k + 1] * tx * ty * dz;
corr_f[2] += fewald_z[i + 0][j + 1][k + 0] * tx * dy * tz;
corr_f[2] += fewald_z[i + 0][j + 1][k + 1] * tx * dy * dz;
corr_f[2] += fewald_z[i + 1][j + 0][k + 0] * dx * ty * tz;
corr_f[2] += fewald_z[i + 1][j + 0][k + 1] * dx * ty * dz;
corr_f[2] += fewald_z[i + 1][j + 1][k + 0] * dx * dy * tz;
corr_f[2] += fewald_z[i + 1][j + 1][k + 1] * dx * dy * dz;
corr_f[2] *= s_z;
/* Interpolation for potential */
*corr_p = 0.;
*corr_p += potewald[i + 0][j + 0][k + 0] * tx * ty * tz;
*corr_p += potewald[i + 0][j + 0][k + 1] * tx * ty * dz;
*corr_p += potewald[i + 0][j + 1][k + 0] * tx * dy * tz;
*corr_p += potewald[i + 0][j + 1][k + 1] * tx * dy * dz;
*corr_p += potewald[i + 1][j + 0][k + 0] * dx * ty * tz;
*corr_p += potewald[i + 1][j + 0][k + 1] * dx * ty * dz;
*corr_p += potewald[i + 1][j + 1][k + 0] * dx * dy * tz;
*corr_p += potewald[i + 1][j + 1][k + 1] * dx * dy * dz;
#else
error("Gravity checking function called without the corresponding flag.");
#endif
}
/**
* @brief Checks whether the file containing the exact accelerations for
* the current choice of parameters already exists.
*
* @param e The #engine.
*/
int gravity_exact_force_file_exits(const struct engine *e) {
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
/* File name */
char file_name[100];
if (e->s->periodic)
sprintf(file_name, "gravity_checks_exact_periodic_step%.4d.dat", e->step);
else
sprintf(file_name, "gravity_checks_exact_step%.4d.dat", e->step);
/* Does the file exist ? */
if (access(file_name, R_OK | W_OK) == 0) {
/* Let's check whether the header matches the parameters of this run */
FILE *file = fopen(file_name, "r");
if (file == NULL) error("Problem reading gravity_check file");
char line[100];
char dummy1[10], dummy2[10];
double newton_G;
int N, periodic;
/* Reads file header */
if (fgets(line, 100, file) != line) error("Problem reading title");
if (fgets(line, 100, file) != line) error("Problem reading G");
sscanf(line, "%s %s %le", dummy1, dummy2, &newton_G);
if (fgets(line, 100, file) != line) error("Problem reading N");
sscanf(line, "%s %s %d", dummy1, dummy2, &N);
if (fgets(line, 100, file) != line) error("Problem reading BC");
sscanf(line, "%s %s %d", dummy1, dummy2, &periodic);
fclose(file);
/* Check whether it matches the current parameters */
if (N == SWIFT_GRAVITY_FORCE_CHECKS && periodic == e->s->periodic &&
(fabs(newton_G - e->physical_constants->const_newton_G) / newton_G <
1e-5)) {
return 1;
}
}
return 0;
#else
error("Gravity checking function called without the corresponding flag.");
return 0;
#endif
}
/**
* @brief Mapper function for the exact gravity calculation.
*/
void gravity_exact_force_compute_mapper(void *map_data, int nr_gparts,
void *extra_data) {
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
/* Unpack the data */
struct gpart *restrict gparts = (struct gpart *)map_data;
struct exact_force_data *data = (struct exact_force_data *)extra_data;
const struct space *s = data->s;
const struct part *parts = s->parts;
const struct spart *sparts = s->sparts;
const struct bpart *bparts = s->bparts;
const struct engine *e = data->e;
const int periodic = s->periodic;
const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
const double const_G = data->const_G;
int counter = 0;
for (int i = 0; i < nr_gparts; ++i) {
struct gpart *gpi = &gparts[i];
/* Get the particle ID */
long long id = 0;
if (gpi->type == swift_type_gas)
id = parts[-gpi->id_or_neg_offset].id;
else if (gpi->type == swift_type_stars)
id = sparts[-gpi->id_or_neg_offset].id;
else if (gpi->type == swift_type_black_hole)
id = bparts[-gpi->id_or_neg_offset].id;
else
id = gpi->id_or_neg_offset;
/* Is the particle active and part of the subset to be tested ? */
if (id % SWIFT_GRAVITY_FORCE_CHECKS == 0 && gpart_is_active(gpi, e)) {
/* Get some information about the particle */
const double pix[3] = {gpi->x[0], gpi->x[1], gpi->x[2]};
const double hi = gravity_get_softening(gpi, e->gravity_properties);
const double hi_inv = 1. / hi;
const double hi_inv3 = hi_inv * hi_inv * hi_inv;
/* Be ready for the calculation */
double a_grav[3] = {0., 0., 0.};
double a_grav_short[3] = {0., 0., 0.};
double a_grav_long[3] = {0., 0., 0.};
double pot = 0.;
/* Interact it with all other particles in the space.*/
for (int j = 0; j < (int)s->nr_gparts; ++j) {
const struct gpart *gpj = &s->gparts[j];
#ifdef SWIFT_DEBUG_CHECKS
if (gpj->time_bin == time_bin_not_created) {
error("Found an extra particle in the gravity check.");
}
#endif
/* No self interaction */
if (gpi == gpj) continue;
/* Compute the pairwise distance. */
double dx = gpj->x[0] - pix[0];
double dy = gpj->x[1] - pix[1];
double dz = gpj->x[2] - pix[2];
/* Now apply periodic BC */
if (periodic) {
dx = nearest(dx, dim[0]);
dy = nearest(dy, dim[1]);
dz = nearest(dz, dim[2]);
}
const double r2 = dx * dx + dy * dy + dz * dz;
const double r_inv = 1. / sqrt(r2);
const double r = r2 * r_inv;
const double mj = gpj->mass;
double f, phi;
if (r >= hi) {
/* Get Newtonian gravity */
f = mj * r_inv * r_inv * r_inv;
phi = -mj * r_inv;
} else {
const double ui = r * hi_inv;
double Wf, Wp;
kernel_grav_eval_force_double(ui, &Wf);
kernel_grav_eval_pot_double(ui, &Wp);
/* Get softened gravity */
f = mj * hi_inv3 * Wf;
phi = mj * hi_inv * Wp;
}
a_grav[0] += f * dx;
a_grav[1] += f * dy;
a_grav[2] += f * dz;
pot += phi;
/* Apply Ewald correction for periodic BC
*
* We also want to check what the tree and mesh do so we want to mimic
* that:
* - a_grav_short is the total acceleration multiplied by the
* short-range correction.
* - a_grav_long is the total acceleration (including Ewald correction)
* minus the short-range acceleration.
*/
if (periodic && r > 1e-5 * hi) {
/* Compute trunctation for long and short range forces */
const double r_s_inv = e->mesh->r_s_inv;
const double u_lr = r * r_s_inv;
double corr_f_lr;
kernel_long_grav_force_eval_double(u_lr, &corr_f_lr);
a_grav_short[0] += f * dx * corr_f_lr;
a_grav_short[1] += f * dy * corr_f_lr;
a_grav_short[2] += f * dz * corr_f_lr;
a_grav_long[0] += f * dx * (1. - corr_f_lr);
a_grav_long[1] += f * dy * (1. - corr_f_lr);
a_grav_long[2] += f * dz * (1. - corr_f_lr);
/* Ewald correction. */
double corr_f[3], corr_pot;
gravity_exact_force_ewald_evaluate(dx, dy, dz, corr_f, &corr_pot);
a_grav[0] += mj * corr_f[0];
a_grav[1] += mj * corr_f[1];
a_grav[2] += mj * corr_f[2];
pot += mj * corr_pot;
a_grav_long[0] += mj * corr_f[0];
a_grav_long[1] += mj * corr_f[1];
a_grav_long[2] += mj * corr_f[2];
}
}
/* Store the exact answer */
for (int k = 0; k < 3; k++) {
gpi->a_grav_exact[k] = a_grav[k] * const_G;
gpi->a_grav_exact_short[k] = a_grav_short[k] * const_G;
gpi->a_grav_exact_long[k] = a_grav_long[k] * const_G;
}
gpi->potential_exact = pot * const_G;
counter++;
}
}
atomic_add(&data->counter_global, counter);
#else
error("Gravity checking function called without the corresponding flag.");
#endif
}
/**
* @brief Run a brute-force gravity calculation for a subset of particles.
*
* All gpart with ID modulo SWIFT_GRAVITY_FORCE_CHECKS will get their forces
* computed.
*
* @param s The #space to use.
* @param e The #engine (to access the current time).
*/
void gravity_exact_force_compute(struct space *s, const struct engine *e) {
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
const ticks tic = getticks();
/* Let's start by checking whether we already computed these forces */
if (gravity_exact_force_file_exits(e)) {
message("Exact accelerations already computed. Skipping calculation.");
fflush(stdout);
return;
}
/* No matching file present ? Do it then */
struct exact_force_data data;
data.e = e;
data.s = s;
data.counter_global = 0;
data.const_G = e->physical_constants->const_newton_G;
threadpool_map(&s->e->threadpool, gravity_exact_force_compute_mapper,
s->gparts, s->nr_gparts, sizeof(struct gpart),
threadpool_auto_chunk_size, &data);
message("Computed exact gravity for %d gparts (took %.3f %s). ",
data.counter_global, clocks_from_ticks(getticks() - tic),
clocks_getunit());
#else
error("Gravity checking function called without the corresponding flag.");
#endif
}
/**
* @brief Check the accuracy of the gravity calculation by comparing the
* accelerations
* to the brute-force computed ones.
*
* All gpart with ID modulo SWIFT_GRAVITY_FORCE_CHECKS will be checked.
*
* @param s The #space to use.
* @param e The #engine (to access the current time).
* @param rel_tol The maximal relative error. Will call error() if one particle
* has a larger error.
*/
void gravity_exact_force_check(struct space *s, const struct engine *e,
float rel_tol) {
#ifdef SWIFT_GRAVITY_FORCE_CHECKS
const struct part *parts = s->parts;
const struct spart *sparts = s->sparts;
const struct bpart *bparts = s->bparts;
/* File name */
char file_name_swift[100];
sprintf(file_name_swift, "gravity_checks_swift_step%.4d_order%d.dat", e->step,
SELF_GRAVITY_MULTIPOLE_ORDER);
/* Creare files and write header */
FILE *file_swift = fopen(file_name_swift, "w");
if (file_swift == NULL) error("Could not create file '%s'.", file_name_swift);
fprintf(file_swift, "# Gravity accuracy test - SWIFT FORCES\n");
fprintf(file_swift, "# G= %16.8e\n", e->physical_constants->const_newton_G);
fprintf(file_swift, "# N= %d\n", SWIFT_GRAVITY_FORCE_CHECKS);
fprintf(file_swift, "# periodic= %d\n", s->periodic);
fprintf(file_swift, "# theta= %16.8e\n", e->gravity_properties->theta_crit);
fprintf(file_swift, "# Git Branch: %s\n", git_branch());
fprintf(file_swift, "# Git Revision: %s\n", git_revision());
fprintf(file_swift,
"# %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s "
"%16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s\n",
"id", "pos[0]", "pos[1]", "pos[2]", "a_swift[0]", "a_swift[1]",
"a_swift[2]", "potential", "a_PM[0]", "a_PM[1]", "a_PM[2]",
"potentialPM", "a_p2p[0]", "a_p2p[1]", "a_p2p[2]", "a_m2p[0]",
"a_m2p[1]", "a_m2p[2]", "a_m2l[0]", "a_m2l[1]", "a_m2l[2]", "n_p2p",
"n_m2p", "n_m2l", "n_PM");
/* Output particle SWIFT accelerations */
for (size_t i = 0; i < s->nr_gparts; ++i) {
struct gpart *gpi = &s->gparts[i];
/* Get the particle ID */
long long id = 0;
if (gpi->type == swift_type_gas)
id = parts[-gpi->id_or_neg_offset].id;
else if (gpi->type == swift_type_stars)
id = sparts[-gpi->id_or_neg_offset].id;
else if (gpi->type == swift_type_black_hole)
id = bparts[-gpi->id_or_neg_offset].id;
else
id = gpi->id_or_neg_offset;
/* Is the particle was active and part of the subset to be tested ? */
if (id % SWIFT_GRAVITY_FORCE_CHECKS == 0 && gpart_is_starting(gpi, e)) {
fprintf(file_swift,
"%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e "
"%16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e "
"%16.8e %16.8e %16.8e %16lld %16lld %16lld %16lld\n",
id, gpi->x[0], gpi->x[1], gpi->x[2],
gpi->a_grav[0] + gpi->a_grav_mesh[0],
gpi->a_grav[1] + gpi->a_grav_mesh[1],
gpi->a_grav[2] + gpi->a_grav_mesh[2],
gravity_get_comoving_potential(gpi), gpi->a_grav_mesh[0],
gpi->a_grav_mesh[1], gpi->a_grav_mesh[2],
gravity_get_comoving_mesh_potential(gpi), gpi->a_grav_p2p[0],
gpi->a_grav_p2p[1], gpi->a_grav_p2p[2], gpi->a_grav_m2p[0],
gpi->a_grav_m2p[1], gpi->a_grav_m2p[2], gpi->a_grav_m2l[0],
gpi->a_grav_m2l[1], gpi->a_grav_m2l[2], gpi->num_interacted_p2p,
gpi->num_interacted_m2p, gpi->num_interacted_m2l,
gpi->num_interacted_pm);
}
}
message("Written SWIFT accelerations in file '%s'.", file_name_swift);
/* Be nice */
fclose(file_swift);
if (!gravity_exact_force_file_exits(e)) {
char file_name_exact[100];
if (s->periodic)
sprintf(file_name_exact, "gravity_checks_exact_periodic_step%.4d.dat",
e->step);
else
sprintf(file_name_exact, "gravity_checks_exact_step%.4d.dat", e->step);
FILE *file_exact = fopen(file_name_exact, "w");
if (file_exact == NULL)
error("Could not create file '%s'.", file_name_exact);
fprintf(file_exact, "# Gravity accuracy test - EXACT FORCES\n");
fprintf(file_exact, "# G= %16.8e\n", e->physical_constants->const_newton_G);
fprintf(file_exact, "# N= %d\n", SWIFT_GRAVITY_FORCE_CHECKS);
fprintf(file_exact, "# periodic= %d\n", s->periodic);
fprintf(file_exact, "# Git Branch: %s\n", git_branch());
fprintf(file_exact, "# Git Revision: %s\n", git_revision());
fprintf(file_exact,
"# %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s "
"%16s %16s %16s\n",
"id", "pos[0]", "pos[1]", "pos[2]", "a_exact[0]", "a_exact[1]",
"a_exact[2]", "potential", "a_exact_short[0]", "a_exact_short[1]",
"a_exact_short[2]", "a_exact_long[0]", "a_exact_long[1]",
"a_exact_long[2]");
/* Output particle exact accelerations */
for (size_t i = 0; i < s->nr_gparts; ++i) {
struct gpart *gpi = &s->gparts[i];
long long id = 0;
if (gpi->type == swift_type_gas)
id = parts[-gpi->id_or_neg_offset].id;
else if (gpi->type == swift_type_stars)
id = sparts[-gpi->id_or_neg_offset].id;
else if (gpi->type == swift_type_black_hole)
id = bparts[-gpi->id_or_neg_offset].id;
else
id = gpi->id_or_neg_offset;
/* Is the particle was active and part of the subset to be tested ? */
if (id % SWIFT_GRAVITY_FORCE_CHECKS == 0 && gpart_is_starting(gpi, e)) {
fprintf(file_exact,
"%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e "
"%16.8e %16.8e %16.8e %16.8e %16.8e %16.8e\n",
id, gpi->x[0], gpi->x[1], gpi->x[2], gpi->a_grav_exact[0],
gpi->a_grav_exact[1], gpi->a_grav_exact[2],
gpi->potential_exact, gpi->a_grav_exact_short[0],
gpi->a_grav_exact_short[1], gpi->a_grav_exact_short[2],
gpi->a_grav_exact_long[0], gpi->a_grav_exact_long[1],
gpi->a_grav_exact_long[2]);
}
}
message("Written exact accelerations in file '%s'.", file_name_exact);
/* Be nice */
fclose(file_exact);
}
#else
error("Gravity checking function called without the corresponding flag.");
#endif
}