/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2020 Stuart McAlpine (stuart.mcalpine@helsinki.fi)
* 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
/* MPI headers. */
#ifdef WITH_MPI
#include
#endif
#include "atomic.h"
#include "engine.h"
#include "hydro_io.h"
#include "io_properties.h"
#include "kernel_hydro.h"
#include "line_of_sight.h"
#include "periodic.h"
#include "version.h"
#include
#include
/**
* @brief Will the line of sight intersect a given cell?
*
* Also return 0 if the cell is empty.
*
* @param c The top level cell.
* @param los The line of sight structure.
*/
static INLINE int does_los_intersect(const struct cell *c,
const struct line_of_sight *los) {
/* Empty cell? */
if (c->hydro.count == 0) return 0;
#ifdef SWIFT_DEBUG_CHECKS
if (c->hydro.h_max <= 0.) error("Invalid h_max for does_los_intersect");
#endif
/* Distance from LOS to left and bottom cell edge. */
const double cx_min = c->loc[los->xaxis];
const double cy_min = c->loc[los->yaxis];
/* Distance from LOS to right and top cell edge. */
const double cx_max = c->loc[los->xaxis] + c->width[los->xaxis];
const double cy_max = c->loc[los->yaxis] + c->width[los->yaxis];
/* Maximum smoothing length of a part in this top level cell. */
const double hsml = c->hydro.h_max * kernel_gamma;
const double hsml2 = hsml * hsml;
double dx, dy;
if (los->periodic) {
dx = min(fabs(nearest(cx_min - los->Xpos, los->dim[los->xaxis])),
fabs(nearest(cx_max - los->Xpos, los->dim[los->xaxis])));
dy = min(fabs(nearest(cy_min - los->Ypos, los->dim[los->yaxis])),
fabs(nearest(cy_max - los->Ypos, los->dim[los->yaxis])));
} else {
dx = fabs(cx_max - los->Xpos);
dy = fabs(cy_max - los->Ypos);
}
/* Is sightline directly within this top level cell? */
if (dx < (1.01 * c->width[los->xaxis]) / 2. &&
dy < (1.01 * c->width[los->yaxis]) / 2.) {
return 1;
/* Could a part from this top level cell smooth into the sightline? */
} else if (dx * dx + dy * dy < hsml2) {
return 1;
/* Don't need to work with this top level cell. */
} else {
return 0;
}
}
/**
* @brief Reads the LOS properties from the param file.
*
* @param dim Space dimensions.
* @param los_params Sightline parameters to save into.
* @param params Swift params to read from.
*/
void los_init(const double dim[3], struct los_props *los_params,
struct swift_params *params) {
/* How many line of sights in each plane. */
los_params->num_along_x =
parser_get_param_int(params, "LineOfSight:num_along_x");
los_params->num_along_y =
parser_get_param_int(params, "LineOfSight:num_along_y");
los_params->num_along_z =
parser_get_param_int(params, "LineOfSight:num_along_z");
/* Min/max range across x,y and z (simulation axes) where random
* LOS's are allowed. */
los_params->allowed_losrange_x[0] = 0.;
los_params->allowed_losrange_x[1] = dim[0];
parser_get_opt_param_double_array(params, "LineOfSight:allowed_los_range_x",
2, los_params->allowed_losrange_x);
los_params->allowed_losrange_y[0] = 0.;
los_params->allowed_losrange_y[1] = dim[1];
parser_get_opt_param_double_array(params, "LineOfSight:allowed_los_range_y",
2, los_params->allowed_losrange_y);
los_params->allowed_losrange_z[0] = 0.;
los_params->allowed_losrange_z[1] = dim[2];
parser_get_opt_param_double_array(params, "LineOfSight:allowed_los_range_z",
2, los_params->allowed_losrange_z);
/* Compute total number of sightlines. */
los_params->num_tot = los_params->num_along_z + los_params->num_along_x +
los_params->num_along_y;
/* Where are we saving them? */
parser_get_param_string(params, "LineOfSight:basename", los_params->basename);
/* Min/max range allowed when sightline is shooting down x,y and z
* (simulation axes). */
los_params->range_when_shooting_down_axis[0][0] = 0.;
los_params->range_when_shooting_down_axis[0][1] = dim[0];
parser_get_opt_param_double_array(
params, "LineOfSight:range_when_shooting_down_x", 2,
los_params->range_when_shooting_down_axis[0]);
los_params->range_when_shooting_down_axis[1][0] = 0.;
los_params->range_when_shooting_down_axis[1][1] = dim[1];
parser_get_opt_param_double_array(
params, "LineOfSight:range_when_shooting_down_y", 2,
los_params->range_when_shooting_down_axis[1]);
los_params->range_when_shooting_down_axis[2][0] = 0.;
los_params->range_when_shooting_down_axis[2][1] = dim[2];
parser_get_opt_param_double_array(
params, "LineOfSight:range_when_shooting_down_z", 2,
los_params->range_when_shooting_down_axis[2]);
}
void los_io_output_check(const struct engine *e) {
/* What kind of run are we working with? */
struct swift_params *params = e->parameter_file;
const int with_cosmology = e->policy & engine_policy_cosmology;
const int with_cooling = e->policy & engine_policy_cooling;
const int with_temperature = e->policy & engine_policy_temperature;
const int with_fof = e->policy & engine_policy_fof;
#ifdef HAVE_VELOCIRAPTOR
const int with_stf = (e->policy & engine_policy_structure_finding) &&
(e->s->gpart_group_data != NULL);
#else
const int with_stf = 0;
#endif
const int with_rt = e->policy & engine_policy_rt;
int num_fields = 0;
struct io_props list[100];
/* Find all the gas output fields */
io_select_hydro_fields(e->s->parts, e->s->xparts, with_cosmology,
with_cooling, with_temperature, with_fof, with_stf,
with_rt, e, &num_fields, list);
/* Loop over each output field */
for (int i = 0; i < num_fields; i++) {
/* Did the user cancel this field? */
char field[PARSER_MAX_LINE_SIZE];
sprintf(field, "SelectOutputLOS:%.*s", FIELD_BUFFER_SIZE, list[i].name);
parser_get_opt_param_int(params, field, 1);
}
}
/**
* @brief Create a #line_of_sight object from its attributes
*/
void create_sightline(const double Xpos, const double Ypos,
enum los_direction xaxis, enum los_direction yaxis,
enum los_direction zaxis, const int periodic,
const double dim[3], struct line_of_sight *los,
const double range_when_shooting_down_axis[2]) {
los->Xpos = Xpos;
los->Ypos = Ypos;
los->particles_in_los_local = 0;
los->particles_in_los_total = 0;
los->xaxis = xaxis;
los->yaxis = yaxis;
los->zaxis = zaxis;
los->periodic = periodic;
los->dim[0] = dim[0];
los->dim[1] = dim[1];
los->dim[2] = dim[2];
los->num_intersecting_top_level_cells = 0;
los->range_when_shooting_down_axis[0] = range_when_shooting_down_axis[0];
los->range_when_shooting_down_axis[1] = range_when_shooting_down_axis[1];
}
/**
* @brief Generates random sightline positions.
*
* Independent sightlines are made for the XY, YZ and XZ planes.
*
* @param Los Structure to store sightlines.
* @param params Sightline parameters.
* @param periodic Is this calculation using periodic BCs.
* @param dim The dimension of the volume along the three axis.
*/
void generate_sightlines(struct line_of_sight *Los,
const struct los_props *params, const int periodic,
const double dim[3]) {
/* Keep track of number of sightlines. */
int count = 0;
/* Sightlines in XY plane, shoots down Z. */
for (int i = 0; i < params->num_along_z; i++) {
double Xpos =
((float)rand() / (float)(RAND_MAX) *
(params->allowed_losrange_x[1] - params->allowed_losrange_x[0])) +
params->allowed_losrange_x[0];
double Ypos =
((float)rand() / (float)(RAND_MAX) *
(params->allowed_losrange_y[1] - params->allowed_losrange_y[0])) +
params->allowed_losrange_y[0];
create_sightline(Xpos, Ypos, simulation_x_axis, simulation_y_axis,
simulation_z_axis, periodic, dim, &Los[count],
params->range_when_shooting_down_axis[simulation_z_axis]);
count++;
}
/* Sightlines in YZ plane, shoots down X. */
for (int i = 0; i < params->num_along_x; i++) {
double Xpos =
((float)rand() / (float)(RAND_MAX) *
(params->allowed_losrange_y[1] - params->allowed_losrange_y[0])) +
params->allowed_losrange_y[0];
double Ypos =
((float)rand() / (float)(RAND_MAX) *
(params->allowed_losrange_z[1] - params->allowed_losrange_z[0])) +
params->allowed_losrange_z[0];
create_sightline(Xpos, Ypos, simulation_y_axis, simulation_z_axis,
simulation_x_axis, periodic, dim, &Los[count],
params->range_when_shooting_down_axis[simulation_x_axis]);
count++;
}
/* Sightlines in XZ plane, shoots down Y. */
for (int i = 0; i < params->num_along_y; i++) {
double Xpos =
((float)rand() / (float)(RAND_MAX) *
(params->allowed_losrange_x[1] - params->allowed_losrange_x[0])) +
params->allowed_losrange_x[0];
double Ypos =
((float)rand() / (float)(RAND_MAX) *
(params->allowed_losrange_z[1] - params->allowed_losrange_z[0])) +
params->allowed_losrange_z[0];
create_sightline(Xpos, Ypos, simulation_x_axis, simulation_z_axis,
simulation_y_axis, periodic, dim, &Los[count],
params->range_when_shooting_down_axis[simulation_y_axis]);
count++;
}
/* Make sure we made the correct ammount */
if (count != params->num_tot)
error("Could not make the right number of sightlines");
}
/**
* @brief Print #line_of_sight information.
*
* @param Los Structure to print.
* @param i The index of the #line_of_sight to dump.
*/
void print_los_info(const struct line_of_sight *Los, const int i) {
message(
"[LOS %i] Xpos:%g Ypos:%g parts_in_los:%i "
"num_intersecting_top_level_cells:%i",
i, Los[i].Xpos, Los[i].Ypos, Los[i].particles_in_los_total,
Los[i].num_intersecting_top_level_cells);
}
/**
* @brief Writes dataset for a given part attribute.
*
* @param props dataset for this attribute.
* @param N number of parts in this line of sight.
* @param j Line of sight ID.
* @param e The engine.
* @param grp HDF5 group to write to.
*/
void write_los_hdf5_dataset(const struct io_props props, const size_t N,
const int j, const struct engine *e, hid_t grp) {
/* Create data space */
const hid_t h_space = H5Screate(H5S_SIMPLE);
if (h_space < 0)
error("Error while creating data space for field '%s'.", props.name);
/* Decide what chunk size to use based on compression */
int log2_chunk_size = e->snapshot_compression > 0 ? 12 : 18;
int rank = 0;
hsize_t shape[2];
hsize_t chunk_shape[2];
if (props.dimension > 1) {
rank = 2;
shape[0] = N;
shape[1] = props.dimension;
chunk_shape[0] = 1 << log2_chunk_size;
chunk_shape[1] = props.dimension;
} else {
rank = 1;
shape[0] = N;
shape[1] = 0;
chunk_shape[0] = 1 << log2_chunk_size;
chunk_shape[1] = 0;
}
/* Make sure the chunks are not larger than the dataset */
if (chunk_shape[0] > N) chunk_shape[0] = N;
/* Change shape of data space */
hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, shape);
if (h_err < 0)
error("Error while changing data space shape for field '%s'.", props.name);
/* Dataset properties */
const hid_t h_prop = H5Pcreate(H5P_DATASET_CREATE);
/* Set chunk size */
h_err = H5Pset_chunk(h_prop, rank, chunk_shape);
if (h_err < 0)
error("Error while setting chunk size (%llu, %llu) for field '%s'.",
(unsigned long long)chunk_shape[0],
(unsigned long long)chunk_shape[1], props.name);
/* Impose check-sum to verify data corruption */
h_err = H5Pset_fletcher32(h_prop);
if (h_err < 0)
error("Error while setting checksum options for field '%s'.", props.name);
/* Impose data compression */
char comp_buffer[32] = "None";
if (e->snapshot_compression > 0) {
h_err = H5Pset_shuffle(h_prop);
if (h_err < 0)
error("Error while setting shuffling options for field '%s'.",
props.name);
h_err = H5Pset_deflate(h_prop, e->snapshot_compression);
if (h_err < 0)
error("Error while setting compression options for field '%s'.",
props.name);
}
/* Allocate temporary buffer */
const size_t num_elements = N * props.dimension;
const size_t typeSize = io_sizeof_type(props.type);
void *temp = NULL;
if (swift_memalign("writebuff", (void **)&temp, IO_BUFFER_ALIGNMENT,
num_elements * typeSize) != 0)
error("Unable to allocate temporary i/o buffer");
/* Copy particle data to temp buffer */
io_copy_temp_buffer(temp, e, props, N, e->internal_units, e->snapshot_units);
/* Create dataset */
char att_name[200];
sprintf(att_name, "/LOS_%04i/%s", j, props.name);
const hid_t h_data = H5Dcreate(grp, att_name, io_hdf5_type(props.type),
h_space, H5P_DEFAULT, h_prop, H5P_DEFAULT);
if (h_data < 0) error("Error while creating dataspace '%s'.", props.name);
/* Write dataset */
herr_t status = H5Dwrite(h_data, io_hdf5_type(props.type), H5S_ALL, H5S_ALL,
H5P_DEFAULT, temp);
if (status < 0) error("Error while writing data array '%s'.", props.name);
/* Write unit conversion factors for this data set */
char buffer[FIELD_BUFFER_SIZE] = {0};
units_cgs_conversion_string(buffer, e->snapshot_units, props.units,
props.scale_factor_exponent);
float baseUnitsExp[5];
units_get_base_unit_exponents_array(baseUnitsExp, props.units);
io_write_attribute_f(h_data, "U_M exponent", baseUnitsExp[UNIT_MASS]);
io_write_attribute_f(h_data, "U_L exponent", baseUnitsExp[UNIT_LENGTH]);
io_write_attribute_f(h_data, "U_t exponent", baseUnitsExp[UNIT_TIME]);
io_write_attribute_f(h_data, "U_I exponent", baseUnitsExp[UNIT_CURRENT]);
io_write_attribute_f(h_data, "U_T exponent", baseUnitsExp[UNIT_TEMPERATURE]);
io_write_attribute_f(h_data, "h-scale exponent", 0.f);
io_write_attribute_f(h_data, "a-scale exponent", props.scale_factor_exponent);
io_write_attribute_s(h_data, "Expression for physical CGS units", buffer);
io_write_attribute_s(h_data, "Lossy compression filter", comp_buffer);
io_write_attribute_b(h_data, "Value stored as physical", props.is_physical);
io_write_attribute_b(h_data, "Property can be converted to comoving",
props.is_convertible_to_comoving);
/* Write the actual number this conversion factor corresponds to */
const double factor =
units_cgs_conversion_factor(e->snapshot_units, props.units);
io_write_attribute_d(
h_data,
"Conversion factor to CGS (not including cosmological corrections)",
factor);
io_write_attribute_d(
h_data,
"Conversion factor to physical CGS (including cosmological corrections)",
factor * pow(e->cosmology->a, props.scale_factor_exponent));
#ifdef SWIFT_DEBUG_CHECKS
if (strlen(props.description) == 0)
error("Invalid (empty) description of the field '%s'", props.name);
#endif
/* Write the full description */
io_write_attribute_s(h_data, "Description", props.description);
/* Free and close everything */
swift_free("writebuff", temp);
H5Pclose(h_prop);
H5Dclose(h_data);
H5Sclose(h_space);
}
/**
* @brief Write parts in LOS to HDF5 file.
*
* @param grp HDF5 group of this LOS.
* @param j LOS ID.
* @param N number of parts in this line of sight.
* @param parts the list of parts in this LOS.
* @param e The engine.
* @param xparts the list of xparts in this LOS.
*/
void write_los_hdf5_datasets(hid_t grp, const int j, const size_t N,
const struct part *parts, const struct engine *e,
const struct xpart *xparts) {
/* What kind of run are we working with? */
struct swift_params *params = e->parameter_file;
const int with_cosmology = e->policy & engine_policy_cosmology;
const int with_cooling = e->policy & engine_policy_cooling;
const int with_temperature = e->policy & engine_policy_temperature;
const int with_fof = e->policy & engine_policy_fof;
#ifdef HAVE_VELOCIRAPTOR
const int with_stf = (e->policy & engine_policy_structure_finding) &&
(e->s->gpart_group_data != NULL);
#else
const int with_stf = 0;
#endif
const int with_rt = e->policy & engine_policy_rt;
int num_fields = 0;
struct io_props list[100];
/* Find all the gas output fields */
io_select_hydro_fields(parts, xparts, with_cosmology, with_cooling,
with_temperature, with_fof, with_stf, with_rt, e,
&num_fields, list);
/* Loop over each output field */
for (int i = 0; i < num_fields; i++) {
/* Did the user cancel this field? */
char field[PARSER_MAX_LINE_SIZE];
sprintf(field, "SelectOutputLOS:%.*s", FIELD_BUFFER_SIZE, list[i].name);
int should_write = parser_get_opt_param_int(params, field, 1);
/* Write (if selected) */
if (should_write) write_los_hdf5_dataset(list[i], N, j, e, grp);
}
}
/**
* @brief Writes HDF5 headers and information groups for this line of sight.
*
* @param h_file HDF5 file reference.
* @param e The engine.
* @param LOS_params The line of sight params.
* @param total_num_parts_in_los The total number of particles in all the LoS.
*/
void write_hdf5_header(hid_t h_file, const struct engine *e,
const struct los_props *LOS_params,
const size_t total_num_parts_in_los) {
/* Open header to write simulation properties */
hid_t h_grp =
H5Gcreate(h_file, "/Header", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
if (h_grp < 0) error("Error while creating file header\n");
/* Convert basic output information to snapshot units */
const double factor_time = units_conversion_factor(
e->internal_units, e->snapshot_units, UNIT_CONV_TIME);
const double factor_length = units_conversion_factor(
e->internal_units, e->snapshot_units, UNIT_CONV_LENGTH);
const double dblTime = e->time * factor_time;
const double dim[3] = {e->s->dim[0] * factor_length,
e->s->dim[1] * factor_length,
e->s->dim[2] * factor_length};
io_write_attribute(h_grp, "BoxSize", DOUBLE, dim, 3);
io_write_attribute_d(h_grp, "Time", dblTime);
io_write_attribute_d(h_grp, "Dimension", (int)hydro_dimension);
io_write_attribute_d(h_grp, "Redshift", e->cosmology->z);
io_write_attribute_d(h_grp, "Scale-factor", e->cosmology->a);
io_write_attribute_s(h_grp, "Code", "SWIFT");
io_write_attribute_s(h_grp, "RunName", e->run_name);
io_write_attribute_s(h_grp, "System", hostname());
io_write_attribute(h_grp, "Shift", DOUBLE, e->s->initial_shift, 3);
/* Write out the particle types */
io_write_part_type_names(h_grp);
/* Write out the time-base */
if (e->policy & engine_policy_cosmology) {
io_write_attribute_d(h_grp, "TimeBase_dloga", e->time_base);
const double delta_t = cosmology_get_timebase(e->cosmology, e->ti_current);
io_write_attribute_d(h_grp, "TimeBase_dt", delta_t);
} else {
io_write_attribute_d(h_grp, "TimeBase_dloga", 0);
io_write_attribute_d(h_grp, "TimeBase_dt", e->time_base);
}
/* Store the time at which the snapshot was written */
time_t tm = time(NULL);
struct tm *timeinfo = localtime(&tm);
char snapshot_date[64];
strftime(snapshot_date, 64, "%T %F %Z", timeinfo);
io_write_attribute_s(h_grp, "SnapshotDate", snapshot_date);
/* GADGET-2 legacy values */
/* Number of particles of each type */
long long N_total[swift_type_count] = {0};
N_total[0] = total_num_parts_in_los;
unsigned int numParticles[swift_type_count] = {0};
unsigned int numParticlesHighWord[swift_type_count] = {0};
for (int ptype = 0; ptype < swift_type_count; ++ptype) {
numParticles[ptype] = (unsigned int)N_total[ptype];
numParticlesHighWord[ptype] = (unsigned int)(N_total[ptype] >> 32);
}
io_write_attribute(h_grp, "NumPart_ThisFile", LONGLONG, N_total,
swift_type_count);
io_write_attribute(h_grp, "NumPart_Total", UINT, numParticles,
swift_type_count);
io_write_attribute(h_grp, "NumPart_Total_HighWord", UINT,
numParticlesHighWord, swift_type_count);
io_write_attribute(h_grp, "TotalNumberOfParticles", LONGLONG, N_total,
swift_type_count);
double MassTable[swift_type_count] = {0};
io_write_attribute(h_grp, "MassTable", DOUBLE, MassTable, swift_type_count);
io_write_attribute(h_grp, "InitialMassTable", DOUBLE,
e->s->initial_mean_mass_particles, swift_type_count);
unsigned int flagEntropy[swift_type_count] = {0};
flagEntropy[0] = writeEntropyFlag();
io_write_attribute(h_grp, "Flag_Entropy_ICs", UINT, flagEntropy,
swift_type_count);
io_write_attribute_i(h_grp, "NumFilesPerSnapshot", 1);
io_write_attribute_i(h_grp, "ThisFile", 0);
io_write_attribute_s(h_grp, "SelectOutput", "Default");
io_write_attribute_i(h_grp, "Virtual", 0);
const int to_write[swift_type_count] = {1}; /* We can only have gas */
io_write_attribute(h_grp, "CanHaveTypes", INT, to_write, swift_type_count);
io_write_attribute_s(h_grp, "OutputType", "LineOfSight");
/* Close group */
H5Gclose(h_grp);
/* Copy metadata from ICs to the file */
ic_info_write_hdf5(e->ics_metadata, h_file);
/* Write all the meta-data */
io_write_meta_data(h_file, e, e->internal_units, e->snapshot_units,
/*fof=*/0);
/* Print the LOS properties */
h_grp = H5Gcreate(h_file, "/LineOfSightParameters", H5P_DEFAULT, H5P_DEFAULT,
H5P_DEFAULT);
if (h_grp < 0) error("Error while creating LOS group");
/* Record this LOS attributes */
const int num_los_per_axis[3] = {LOS_params->num_along_x,
LOS_params->num_along_y,
LOS_params->num_along_z};
io_write_attribute(h_grp, "NumLineOfSight_PerAxis", INT, num_los_per_axis, 3);
io_write_attribute(h_grp, "NumLineOfSight_Total", INT, &LOS_params->num_tot,
1);
io_write_attribute(h_grp, "AllowedLOSRangeX", DOUBLE,
LOS_params->allowed_losrange_x, 2);
io_write_attribute(h_grp, "AllowedLOSRangeY", DOUBLE,
LOS_params->allowed_losrange_y, 2);
io_write_attribute(h_grp, "AllowedLOSRangeZ", DOUBLE,
LOS_params->allowed_losrange_z, 2);
H5Gclose(h_grp);
}
/**
* @brief Loop over each part to see which ones intersect the LOS.
*
* @param map_data The parts.
* @param count The number of parts.
* @param extra_data The line_of_sight structure for this LOS.
*/
void los_first_loop_mapper(void *restrict map_data, int count,
void *restrict extra_data) {
struct line_of_sight *LOS_list = (struct line_of_sight *)extra_data;
const struct part *parts = (struct part *)map_data;
size_t los_particle_count = 0;
/* Loop over each part to find those in LOS. */
for (int i = 0; i < count; i++) {
/* Don't consider inhibited parts. */
if (parts[i].time_bin == time_bin_inhibited) continue;
/* Don't consider part if outwith allowed z-range. */
if (parts[i].x[LOS_list->zaxis] <
LOS_list->range_when_shooting_down_axis[0] ||
parts[i].x[LOS_list->zaxis] >
LOS_list->range_when_shooting_down_axis[1])
continue;
/* Distance from this part to LOS along x dim. */
double dx = parts[i].x[LOS_list->xaxis] - LOS_list->Xpos;
/* Periodic wrap. */
if (LOS_list->periodic) dx = nearest(dx, LOS_list->dim[LOS_list->xaxis]);
/* Square. */
const double dx2 = dx * dx;
/* Smoothing length of this part. */
const double hsml = parts[i].h * kernel_gamma;
const double hsml2 = hsml * hsml;
/* Does this particle fall into our LOS? */
if (dx2 < hsml2) {
/* Distance from this part to LOS along y dim. */
double dy = parts[i].x[LOS_list->yaxis] - LOS_list->Ypos;
/* Periodic wrap. */
if (LOS_list->periodic) dy = nearest(dy, LOS_list->dim[LOS_list->yaxis]);
/* Square. */
const double dy2 = dy * dy;
/* Does this part still fall into our LOS? */
if (dy2 < hsml2) {
/* 2D distance to LOS. */
if (dx2 + dy2 <= hsml2) {
/* We've found one. */
los_particle_count++;
}
}
}
} /* End of loop over all parts */
atomic_add(&LOS_list->particles_in_los_local, los_particle_count);
}
/**
* @brief Find all top level cells that a LOS will intersect.
*
* This includes the top level cells the LOS directly passes through
* and the neighbouring top level cells whose parts could smooth into the LOS.
*
* @param e The engine.
* @param los The line_of_sight structure.
* @param los_cells_top (return) Array indicating whether this cell is
* intersected.
* @param cells The array of top-level cells.
* @param local_cells_with_particles The list of local non-empty top-level
* cells.
* @param nr_local_cells_with_particles The number of local non-empty top-level
* cells.
*/
void find_intersecting_top_level_cells(
const struct engine *e, struct line_of_sight *los,
int *restrict los_cells_top, const struct cell *cells,
const int *restrict local_cells_with_particles,
const int nr_local_cells_with_particles) {
/* Keep track of how many top level cells we intersect. */
int num_intersecting_top_level_cells = 0;
/* Loop over each top level cell */
for (int n = 0; n < nr_local_cells_with_particles; n++) {
/* This top level cell. */
const struct cell *c = &cells[local_cells_with_particles[n]];
if (does_los_intersect(c, los)) {
num_intersecting_top_level_cells++;
los_cells_top[n] = 1;
}
}
#ifdef WITH_MPI
if (MPI_Allreduce(MPI_IN_PLACE, &num_intersecting_top_level_cells, 1, MPI_INT,
MPI_SUM, MPI_COMM_WORLD) != MPI_SUCCESS)
error("Failed to allreduce num_intersecting_top_level_cells.");
#endif
/* Store how many top level cells this LOS intersects. */
los->num_intersecting_top_level_cells = num_intersecting_top_level_cells;
}
/**
* @brief Main work function for computing line of sights.
*
* 1) Construct N random line of sight positions.
* 2) Loop over each line of sight.
* - 2.1) Find which top level cells sightline intersects.
* - 2.2) Loop over each part in these top level cells to see which intersect
* sightline.
* - 2.3) Use this count to construct a LOS parts/xparts array.
* - 2.4) Loop over each part and extract those in sightline to new array.
* - 2.5) Save sightline parts to HDF5 file.
*
* @param e The engine.
*/
void do_line_of_sight(struct engine *e) {
/* Start counting. */
const ticks tic = getticks();
const struct space *s = e->s;
const int periodic = s->periodic;
const double dim[3] = {s->dim[0], s->dim[1], s->dim[2]};
const struct los_props *LOS_params = e->los_properties;
const int verbose = e->verbose;
/* Start by generating the random sightline positions. */
struct line_of_sight *LOS_list = (struct line_of_sight *)malloc(
LOS_params->num_tot * sizeof(struct line_of_sight));
if (e->nodeID == 0) {
generate_sightlines(LOS_list, LOS_params, periodic, dim);
if (verbose)
message("Generated %i random sightlines.", LOS_params->num_tot);
}
#ifdef WITH_MPI
/* Share the list of LoS with all the MPI ranks */
MPI_Bcast(LOS_list, LOS_params->num_tot * sizeof(struct line_of_sight),
MPI_BYTE, 0, MPI_COMM_WORLD);
#endif
/* Node 0 creates the HDF5 file. */
hid_t h_file = -1, h_grp = -1, h_props = -1;
char fileName[256], groupName[200];
if (e->nodeID == 0) {
sprintf(fileName, "%s_%04i.hdf5", LOS_params->basename,
e->los_output_count);
if (verbose) message("Creating LOS file: %s", fileName);
/* Set the minimal API version to avoid issues with advanced features */
h_props = H5Pcreate(H5P_FILE_ACCESS);
hid_t err = H5Pset_libver_bounds(h_props, HDF5_LOWEST_FILE_FORMAT_VERSION,
HDF5_HIGHEST_FILE_FORMAT_VERSION);
if (err < 0) error("Error setting the hdf5 API version");
h_file = H5Fcreate(fileName, H5F_ACC_TRUNC, H5P_DEFAULT, h_props);
if (h_file < 0) error("Error while opening file '%s'.", fileName);
}
#ifdef WITH_MPI
MPI_Barrier(MPI_COMM_WORLD);
#endif
/* Keep track of the total number of parts in all sightlines. */
size_t total_num_parts_in_los = 0;
/* ------------------------------- */
/* Main loop over each random LOS. */
/* ------------------------------- */
/* Get list of local non-empty top level cells. */
const struct cell *cells = e->s->cells_top;
const int *local_cells_with_particles = e->s->local_cells_with_particles_top;
const int nr_local_cells_with_particles = s->nr_local_cells_with_particles;
/* Loop over each random LOS. */
for (int j = 0; j < LOS_params->num_tot; j++) {
/* Create empty top level cell list for this LOS */
int *los_cells_top = (int *)swift_malloc(
"tl_cells_los", nr_local_cells_with_particles * sizeof(int));
bzero(los_cells_top, nr_local_cells_with_particles * sizeof(int));
/* Find all top level cells this LOS will intersect. */
find_intersecting_top_level_cells(e, &LOS_list[j], los_cells_top, cells,
local_cells_with_particles,
nr_local_cells_with_particles);
/* Next count all the parts that intersect with this line of sight */
for (int n = 0; n < nr_local_cells_with_particles; n++) {
if (los_cells_top[n] == 1) {
const struct cell *c = &cells[local_cells_with_particles[n]];
threadpool_map(&s->e->threadpool, los_first_loop_mapper, c->hydro.parts,
c->hydro.count, sizeof(struct part),
threadpool_auto_chunk_size, &LOS_list[j]);
}
}
#ifdef SWIFT_DEBUG_CHECKS
/* Confirm we are capturing all the parts that intersect the LOS by redoing
* the count looping over all parts in the space (not just those in the
* subset of top level cells). */
struct part *parts = s->parts;
const size_t nr_parts = s->nr_parts;
const int old_particles_in_los_local = LOS_list[j].particles_in_los_local;
LOS_list[j].particles_in_los_local = 0;
/* Count all parts that intersect with this line of sight. */
threadpool_map(&s->e->threadpool, los_first_loop_mapper, parts, nr_parts,
sizeof(struct part), threadpool_auto_chunk_size,
&LOS_list[j]);
/* Make sure we get the same answer as above. */
if (old_particles_in_los_local != LOS_list[j].particles_in_los_local)
error("Space vs cells don't match s:%d != c:%d",
LOS_list[j].particles_in_los_local, old_particles_in_los_local);
#endif
#ifdef WITH_MPI
/* Make sure all nodes know how many parts are in this LOS */
int *counts = (int *)malloc(sizeof(int) * e->nr_nodes);
int *offsets = (int *)malloc(sizeof(int) * e->nr_nodes);
/* How many parts does each rank have for this LOS? */
MPI_Allgather(&LOS_list[j].particles_in_los_local, 1, MPI_INT, counts, 1,
MPI_INT, MPI_COMM_WORLD);
int offset_count = 0;
for (int k = 0; k < e->nr_nodes; k++) {
/* Total parts in this LOS. */
LOS_list[j].particles_in_los_total += counts[k];
/* Counts and offsets for Gatherv. */
offsets[k] = offset_count;
offset_count += counts[k];
}
#else
LOS_list[j].particles_in_los_total = LOS_list[j].particles_in_los_local;
#endif
total_num_parts_in_los += LOS_list[j].particles_in_los_total;
/* Print information about this LOS */
if (e->nodeID == 0) print_los_info(LOS_list, j);
/* Don't work with empty LOS */
if (LOS_list[j].particles_in_los_total == 0) {
if (e->nodeID == 0) {
message("*WARNING* LOS %i is empty", j);
print_los_info(LOS_list, j);
}
#ifdef WITH_MPI
free(counts);
counts = NULL;
free(offsets);
offsets = NULL;
#endif
swift_free("tl_cells_los", los_cells_top);
continue;
}
/* Setup LOS part and xpart structures. */
struct part *LOS_parts = NULL;
struct xpart *LOS_xparts = NULL;
struct gpart *LOS_gparts = NULL;
/* Rank 0 allocates more space as it will gather all the data for writing */
if (e->nodeID == 0) {
if ((LOS_parts = (struct part *)swift_malloc(
"los_parts_array",
sizeof(struct part) * LOS_list[j].particles_in_los_total)) ==
NULL)
error("Failed to allocate LOS part memory.");
if ((LOS_xparts = (struct xpart *)swift_malloc(
"los_xparts_array",
sizeof(struct xpart) * LOS_list[j].particles_in_los_total)) ==
NULL)
error("Failed to allocate LOS xpart memory.");
if ((LOS_gparts = (struct gpart *)swift_malloc(
"los_gparts_array",
sizeof(struct gpart) * LOS_list[j].particles_in_los_total)) ==
NULL)
error("Failed to allocate LOS gpart memory.");
} else {
if ((LOS_parts = (struct part *)swift_malloc(
"los_parts_array",
sizeof(struct part) * LOS_list[j].particles_in_los_local)) ==
NULL)
error("Failed to allocate LOS part memory.");
if ((LOS_xparts = (struct xpart *)swift_malloc(
"los_xparts_array",
sizeof(struct xpart) * LOS_list[j].particles_in_los_local)) ==
NULL)
error("Failed to allocate LOS xpart memory.");
if ((LOS_gparts = (struct gpart *)swift_malloc(
"los_gparts_array",
sizeof(struct gpart) * LOS_list[j].particles_in_los_local)) ==
NULL)
error("Failed to allocate LOS gpart memory.");
}
/* Loop over each part again, pulling out those in LOS. */
int count = 0;
for (int n = 0; n < e->s->nr_local_cells_with_particles; ++n) {
if (los_cells_top[n] == 0) continue;
const struct cell *c = &cells[local_cells_with_particles[n]];
const struct part *cell_parts = c->hydro.parts;
const struct xpart *cell_xparts = c->hydro.xparts;
const size_t num_parts_in_cell = c->hydro.count;
for (size_t i = 0; i < num_parts_in_cell; i++) {
/* Don't consider inhibited parts. */
if (cell_parts[i].time_bin == time_bin_inhibited) continue;
if (cell_parts[i].time_bin == time_bin_not_created) continue;
/* Don't consider part if outwith allowed z-range. */
if (cell_parts[i].x[LOS_list[j].zaxis] <
LOS_list[j].range_when_shooting_down_axis[0] ||
cell_parts[i].x[LOS_list[j].zaxis] >
LOS_list[j].range_when_shooting_down_axis[1])
continue;
/* Distance from this part to LOS along x dim. */
double dx = cell_parts[i].x[LOS_list[j].xaxis] - LOS_list[j].Xpos;
/* Periodic wrap. */
if (LOS_list[j].periodic)
dx = nearest(dx, LOS_list[j].dim[LOS_list[j].xaxis]);
/* Square */
const double dx2 = dx * dx;
/* Smoothing length of this part. */
const double hsml = cell_parts[i].h * kernel_gamma;
const double hsml2 = hsml * hsml;
/* Does this part fall into our LOS? */
if (dx2 < hsml2) {
/* Distance from this part to LOS along y dim. */
double dy = cell_parts[i].x[LOS_list[j].yaxis] - LOS_list[j].Ypos;
/* Periodic wrap. */
if (LOS_list[j].periodic)
dy = nearest(dy, LOS_list[j].dim[LOS_list[j].yaxis]);
/* Square */
const double dy2 = dy * dy;
/* Does this part still fall into our LOS? */
if (dy2 < hsml2) {
/* 2D distance to LOS. */
if (dx2 + dy2 <= hsml2) {
/* Store part and xpart properties. */
memcpy(&LOS_parts[count], &cell_parts[i], sizeof(struct part));
memcpy(&LOS_xparts[count], &cell_xparts[i], sizeof(struct xpart));
memcpy(&LOS_gparts[count], cell_parts[i].gpart,
sizeof(struct gpart));
count++;
}
}
}
}
}
#ifdef SWIFT_DEBUG_CHECKS
if (count != LOS_list[j].particles_in_los_local)
error("LOS counts don't add up");
#endif
#ifdef WITH_MPI
/* Collect all parts in this LOS to rank 0. */
if (e->nodeID == 0) {
MPI_Gatherv(MPI_IN_PLACE, 0, part_mpi_type, LOS_parts, counts, offsets,
part_mpi_type, 0, MPI_COMM_WORLD);
MPI_Gatherv(MPI_IN_PLACE, 0, xpart_mpi_type, LOS_xparts, counts, offsets,
xpart_mpi_type, 0, MPI_COMM_WORLD);
MPI_Gatherv(MPI_IN_PLACE, 0, gpart_mpi_type, LOS_gparts, counts, offsets,
gpart_mpi_type, 0, MPI_COMM_WORLD);
} else {
MPI_Gatherv(LOS_parts, LOS_list[j].particles_in_los_local, part_mpi_type,
LOS_parts, counts, offsets, part_mpi_type, 0, MPI_COMM_WORLD);
MPI_Gatherv(LOS_xparts, LOS_list[j].particles_in_los_local,
xpart_mpi_type, LOS_xparts, counts, offsets, xpart_mpi_type,
0, MPI_COMM_WORLD);
MPI_Gatherv(LOS_gparts, LOS_list[j].particles_in_los_local,
gpart_mpi_type, LOS_gparts, counts, offsets, gpart_mpi_type,
0, MPI_COMM_WORLD);
}
#endif
/* Re-instate part->gpart pointer on teh receiving side */
if (e->nodeID == 0) {
#ifdef WITH_MPI
for (int i = 0; i < LOS_list[j].particles_in_los_total; ++i) {
LOS_parts[i].gpart = &LOS_gparts[i];
LOS_gparts[i].id_or_neg_offset = -i;
}
#endif
}
/* Rank 0 writes particles to file. */
if (e->nodeID == 0) {
/* Create HDF5 group for this LOS */
sprintf(groupName, "/LOS_%04i", j);
h_grp =
H5Gcreate(h_file, groupName, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
if (h_grp < 0) error("Error while creating LOS HDF5 group\n");
/* Record this LOS attributes */
io_write_attribute(h_grp, "NumParts", INT,
&LOS_list[j].particles_in_los_total, 1);
io_write_attribute(h_grp, "Xaxis", INT, &LOS_list[j].xaxis, 1);
io_write_attribute(h_grp, "Yaxis", INT, &LOS_list[j].yaxis, 1);
io_write_attribute(h_grp, "Zaxis", INT, &LOS_list[j].zaxis, 1);
io_write_attribute(h_grp, "Xpos", DOUBLE, &LOS_list[j].Xpos, 1);
io_write_attribute(h_grp, "Ypos", DOUBLE, &LOS_list[j].Ypos, 1);
#ifdef SWIFT_DEBUG_CHECKS
for (int i = 0; i < LOS_list[j].particles_in_los_total; ++i) {
if (LOS_parts[i].gpart != &LOS_gparts[i]) error("Incorrect pointers!");
}
#endif
/* Write the data for this LOS */
write_los_hdf5_datasets(h_grp, j, LOS_list[j].particles_in_los_total,
LOS_parts, e, LOS_xparts);
/* Close HDF5 group */
H5Gclose(h_grp);
}
/* Free up some memory */
#ifdef WITH_MPI
free(counts);
free(offsets);
#endif
swift_free("tl_cells_los", los_cells_top);
swift_free("los_parts_array", LOS_parts);
swift_free("los_xparts_array", LOS_xparts);
swift_free("los_gparts_array", LOS_gparts);
} /* End of loop over each LOS */
if (e->nodeID == 0) {
/* Write header */
write_hdf5_header(h_file, e, LOS_params, total_num_parts_in_los);
/* Close HDF5 file */
H5Fclose(h_file);
H5Pclose(h_props);
}
/* Up the LOS counter. */
e->los_output_count++;
/* How long did we take? */
if (verbose)
message("took %.3f %s.", clocks_from_ticks(getticks() - tic),
clocks_getunit());
}
/**
* @brief Write a los_props struct to the given FILE as a stream of bytes.
*
* @param internal_los the struct
* @param stream the file stream
*/
void los_struct_dump(const struct los_props *internal_los, FILE *stream) {
restart_write_blocks((void *)internal_los, sizeof(struct los_props), 1,
stream, "losparams", "los params");
}
/**
* @brief Restore a los_props struct from the given FILE as a stream of
* bytes.
*
* @param internal_los the struct
* @param stream the file stream
*/
void los_struct_restore(const struct los_props *internal_los, FILE *stream) {
restart_read_blocks((void *)internal_los, sizeof(struct los_props), 1, stream,
NULL, "los params");
}