Version: SMASH-3.1
grid.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2015,2017-2023
4  * SMASH Team
5  *
6  * GNU General Public License (GPLv3 or later)
7  *
8  */
9 
10 #include "smash/grid.h"
11 
12 #include <stdexcept>
13 
14 #include "smash/algorithms.h"
15 #include "smash/fourvector.h"
16 #include "smash/logging.h"
17 #include "smash/particledata.h"
18 #include "smash/threevector.h"
19 
20 namespace std {
26 template <typename T>
27 static std::ostream &operator<<(std::ostream &out, const std::vector<T> &v) {
28  auto column = out.tellp();
29  out << "{ ";
30  for (const auto &x : v) {
31  if (out.tellp() - column >= 100) {
32  out << '\n';
33  column = out.tellp();
34  }
35  out << x << ' ';
36  }
37  return out << '}';
38 }
39 
45 template <typename T>
46 static std::ostream &operator<<(std::ostream &out,
47  const std::initializer_list<T> &v) {
48  auto column = out.tellp();
49  out << "{ ";
50  for (const auto &x : v) {
51  if (out.tellp() - column >= 100) {
52  out << '\n';
53  column = out.tellp();
54  }
55  out << x << ' ';
56  }
57  return out << '}';
58 }
59 
65 template <typename T, std::size_t N>
66 static std::ostream &operator<<(std::ostream &out, const std::array<T, N> &a) {
67  auto column = out.tellp();
68  out << "{ ";
69  for (const auto &x : a) {
70  if (out.tellp() - column >= 100) {
71  out << '\n';
72  column = out.tellp();
73  }
74  out << x << ' ';
75  }
76  return out << '}';
77 }
78 } // namespace std
79 
80 namespace smash {
81 static constexpr int LGrid = LogArea::Grid::id;
82 
84 // GridBase
85 
86 std::pair<std::array<double, 3>, std::array<double, 3>>
88  std::pair<std::array<double, 3>, std::array<double, 3>> r;
89  auto &min_position = r.first;
90  auto &length = r.second;
91 
92  // intialize min and max position arrays with the position of the first
93  // particle in the list
94  const auto &first_position = particles.front().position();
95  min_position = {{first_position[1], first_position[2], first_position[3]}};
96  auto max_position = min_position;
97  for (const auto &p : particles) {
98  const auto &pos = p.position();
99  min_position[0] = std::min(min_position[0], pos[1]);
100  min_position[1] = std::min(min_position[1], pos[2]);
101  min_position[2] = std::min(min_position[2], pos[3]);
102  max_position[0] = std::max(max_position[0], pos[1]);
103  max_position[1] = std::max(max_position[1], pos[2]);
104  max_position[2] = std::max(max_position[2], pos[3]);
105  }
106  length[0] = max_position[0] - min_position[0];
107  length[1] = max_position[1] - min_position[1];
108  length[2] = max_position[2] - min_position[2];
109  return r;
110 }
111 
113 // Grid
114 
115 template <GridOptions O>
116 Grid<O>::Grid(const std::pair<std::array<double, 3>, std::array<double, 3>>
117  &min_and_length,
118  const Particles &particles, double max_interaction_length,
119  double timestep_duration, CellNumberLimitation limit,
120  const bool include_unformed_particles, CellSizeStrategy strategy)
121  : length_(min_and_length.second) {
122  const auto min_position = min_and_length.first;
123  const SizeType particle_count = particles.size();
124 
125  // very simple setup for non-periodic boundaries and largest cellsize strategy
126  if (O == GridOptions::Normal && strategy == CellSizeStrategy::Largest) {
127  number_of_cells_ = {1, 1, 1};
128  cell_volume_ = length_[0] * length_[1] * length_[2];
129  cells_.clear();
130  cells_.reserve(1);
131  cells_.emplace_back(particles.copy_to_vector());
132  return;
133  }
134 
135  // The number of cells is determined by the min and max coordinates where
136  // particles are positioned and the maximal interaction length (which equals
137  // the length of a cell).
138  // But don't let the number of cells exceed the actual number of particles.
139  // That would be overkill. Let max_cells³ ≤ particle_count (conversion to
140  // int truncates). Limit only applied for geometric criteiron, where grid
141  // is an optimisation and cells can be made larger.
142  const int max_cells =
143  (O == GridOptions::Normal)
144  ? std::cbrt(particle_count)
145  : std::max(2, static_cast<int>(std::cbrt(particle_count)));
146 
147  // This normally equals 1/max_interaction_length. If the number of cells
148  // is reduced (because of low density) then this value is smaller. If only
149  // one cell is used than this value might also be larger.
150  std::array<double, 3> index_factor = {1. / max_interaction_length,
151  1. / max_interaction_length,
152  1. / max_interaction_length};
153  for (std::size_t i = 0; i < number_of_cells_.size(); ++i) {
154  if (strategy == CellSizeStrategy::Largest) {
155  number_of_cells_[i] = 2;
156  } else if (unlikely(length_[i] >
157  std::numeric_limits<int>::max() / index_factor[i])) {
158  throw std::overflow_error(
159  "An integer overflow would occur constructing the system grid.\n"
160  "Impossible to (further) simulate the provided system using "
161  "SMASH.\n"
162  "Refer to the user guide for more information (see the Modi "
163  "page).");
164  } else {
165  number_of_cells_[i] =
166  static_cast<int>(std::floor(length_[i] * index_factor[i]));
167  }
169  // In case of zero cells, make at least one cell that is then smaller than
170  // the minimal cell length. This is ok for all setups, since all particles
171  // are inside the same cell, except for the box with periodic boundary
172  // conditions, where we need a 2x2x2 grid.
173  number_of_cells_[i] = 1;
174  } else if (number_of_cells_[i] < 2 &&
176  // Double the minimal cell length exceeds the length of the box, but we
177  // need at least 2x2x2 cells for periodic boundaries.
178  std::string error_box_too_small =
179  "Input error: With the chosen time step (Delta_Time), your box is\n"
180  "too small for the grid. Using the provided time step, the minimal\n"
181  "length of the box should be " +
182  std::to_string(2 * max_interaction_length) +
183  "fm. Using a smaller time step\n"
184  "will reduce the minimal needed box size. The use of test particles\n"
185  "also helps reducing the minimum needed size. Have a look to the\n"
186  "user guide (e.g. box modus page) for further information.\n"
187  "Please, adjust your config file and run SMASH again.";
188  throw std::runtime_error(error_box_too_small);
189  } else if (limit == CellNumberLimitation::ParticleNumber &&
190  number_of_cells_[i] > max_cells) {
191  number_of_cells_[i] = max_cells;
192  }
193  // Only bother rescaling the index_factor if the grid length is large enough
194  // for 1 full min. cell length, since all particles are anyway placed in the
195  // first cell along the i-th axis
196  if (length_[i] >= max_interaction_length) {
197  index_factor[i] = number_of_cells_[i] / length_[i];
198  // std::nextafter implements a safety margin so that no valid position
199  // inside the grid can reference an out-of-bounds cell
200  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
201  index_factor[i] = std::nextafter(index_factor[i], 0.);
202  }
203  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
204  }
205  }
206 
207  if (O == GridOptions::Normal &&
208  all_of(number_of_cells_, [](SizeType n) { return n <= 2; })) {
209  // dilute limit:
210  // the grid would have <= 2x2x2 cells, meaning every particle has to be
211  // compared with every other particle anyway. Then we can just as well
212  // fall back to not using the grid at all
213  // For a grid with periodic boundaries the situation is different and we
214  // never want to have a grid smaller than 2x2x2.
215  logg[LGrid].debug(
216  "There would only be ", number_of_cells_,
217  " cells. Therefore the Grid falls back to a single cell / "
218  "particle list.");
219  number_of_cells_ = {1, 1, 1};
220  cell_volume_ = length_[0] * length_[1] * length_[2];
221  if (include_unformed_particles) {
222  cells_.clear();
223  cells_.reserve(1);
224  cells_.emplace_back(particles.copy_to_vector());
225  } else {
226  // filter out the particles that can not interact
227  cells_.resize(1);
228  cells_.front().reserve(particles.size());
229  std::copy_if(particles.begin(), particles.end(),
230  std::back_inserter(cells_.front()),
231  [&](const ParticleData &p) {
232  return p.xsec_scaling_factor(timestep_duration) > 0.0;
233  });
234  }
235  } else {
236  // construct a normal grid
237 
239  (length_[1] / number_of_cells_[1]) *
240  (length_[2] / number_of_cells_[2]);
241 
242  logg[LGrid].debug("min: ", min_position, "\nlength: ", length_,
243  "\ncell_volume: ", cell_volume_,
244  "\ncells: ", number_of_cells_,
245  "\nindex_factor: ", index_factor);
246 
247  // After the grid parameters are determined, we can start placing the
248  // particles in cells.
249  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
250  number_of_cells_[2]);
251 
252  // Returns the one-dimensional cell-index from the position vector inside
253  // the grid.
254  // This simply calculates the distance to min_position and multiplies it
255  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
256  auto &&cell_index_for = [&](const ParticleData &p) {
257  return make_index(
258  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
259  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
260  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
261  };
262  for (const auto &p : particles) {
263  if (!include_unformed_particles &&
264  (p.xsec_scaling_factor(timestep_duration) <= 0.0)) {
265  continue;
266  }
267  const auto idx = cell_index_for(p);
268 #ifndef NDEBUG
269  if (idx >= SizeType(cells_.size())) {
270  logg[LGrid].fatal(
272  "\nan out-of-bounds access would be necessary for the "
273  "particle ",
274  p,
275  "\nfor a grid with the following parameters:\nmin: ", min_position,
276  "\nlength: ", length_, "\ncells: ", number_of_cells_,
277  "\nindex_factor: ", index_factor, "\ncells_.size: ", cells_.size(),
278  "\nrequested index: ", idx);
279  throw std::runtime_error("out-of-bounds grid access on construction");
280  }
281 #endif
282  cells_[idx].push_back(p);
283  }
284  }
285 
286  logg[LGrid].debug(cells_);
287 }
288 
289 template <GridOptions Options>
291  SizeType x, SizeType y, SizeType z) const {
292  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
293 }
294 
295 static const std::initializer_list<GridBase::SizeType> ZERO{0};
296 static const std::initializer_list<GridBase::SizeType> ZERO_ONE{0, 1};
297 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO{-1, 0};
298 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO_ONE{-1, 0,
299  1};
300 
301 template <>
304  const std::function<void(const ParticleList &)> &search_cell_callback,
305  const std::function<void(const ParticleList &, const ParticleList &)>
306  &neighbor_cell_callback) const {
307  std::array<SizeType, 3> search_index;
308  SizeType &x = search_index[0];
309  SizeType &y = search_index[1];
310  SizeType &z = search_index[2];
311  SizeType search_cell_index = 0;
312  for (z = 0; z < number_of_cells_[2]; ++z) {
313  for (y = 0; y < number_of_cells_[1]; ++y) {
314  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
315  assert(search_cell_index == make_index(search_index));
316  assert(search_cell_index >= 0);
317  assert(search_cell_index < SizeType(cells_.size()));
318  const ParticleList &search = cells_[search_cell_index];
319  search_cell_callback(search);
320 
321  const auto &dz_list = z == number_of_cells_[2] - 1 ? ZERO : ZERO_ONE;
322  const auto &dy_list = number_of_cells_[1] == 1 ? ZERO
323  : y == 0 ? ZERO_ONE
324  : y == number_of_cells_[1] - 1
327  const auto &dx_list = number_of_cells_[0] == 1 ? ZERO
328  : x == 0 ? ZERO_ONE
329  : x == number_of_cells_[0] - 1
332  for (SizeType dz : dz_list) {
333  for (SizeType dy : dy_list) {
334  for (SizeType dx : dx_list) {
335  const auto di = make_index(dx, dy, dz);
336  if (di > 0) {
337  neighbor_cell_callback(search, cells_[search_cell_index + di]);
338  }
339  }
340  }
341  }
342  }
343  }
344  }
345 }
346 
356 
363 };
364 
365 template <>
368  const std::function<void(const ParticleList &)> &search_cell_callback,
369  const std::function<void(const ParticleList &, const ParticleList &)>
370  &neighbor_cell_callback) const {
371  std::array<SizeType, 3> search_index;
372  SizeType &x = search_index[0];
373  SizeType &y = search_index[1];
374  SizeType &z = search_index[2];
375  SizeType search_cell_index = 0;
376 
377  // defaults:
378  std::array<NeighborLookup, 2> dz_list;
379  std::array<NeighborLookup, 3> dy_list;
380  std::array<NeighborLookup, 3> dx_list;
381 
382  assert(number_of_cells_[2] >= 2);
383  assert(number_of_cells_[1] >= 2);
384  assert(number_of_cells_[0] >= 2);
385 
386  for (z = 0; z < number_of_cells_[2]; ++z) {
387  dz_list[0].index = z;
388  dz_list[1].index = z + 1;
389  if (dz_list[1].index == number_of_cells_[2]) {
390  dz_list[1].index = 0;
391  // last z in the loop, so no need to reset wrap again
392  dz_list[1].wrap = NeedsToWrap::MinusLength;
393  }
394  for (y = 0; y < number_of_cells_[1]; ++y) {
395  dy_list[0].index = y;
396  dy_list[1].index = y - 1;
397  dy_list[2].index = y + 1;
398  dy_list[2].wrap = NeedsToWrap::No;
399  if (y == 0) {
400  dy_list[1] = dy_list[2];
401  dy_list[2].index = number_of_cells_[1] - 1;
402  dy_list[2].wrap = NeedsToWrap::PlusLength;
403  } else if (dy_list[2].index == number_of_cells_[1]) {
404  dy_list[2].index = 0;
405  dy_list[2].wrap = NeedsToWrap::MinusLength;
406  }
407  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
408  dx_list[0].index = x;
409  dx_list[1].index = x - 1;
410  dx_list[2].index = x + 1;
411  dx_list[2].wrap = NeedsToWrap::No;
412  if (x == 0) {
413  dx_list[1] = dx_list[2];
414  dx_list[2].index = number_of_cells_[0] - 1;
415  dx_list[2].wrap = NeedsToWrap::PlusLength;
416  } else if (dx_list[2].index == number_of_cells_[0]) {
417  dx_list[2].index = 0;
418  dx_list[2].wrap = NeedsToWrap::MinusLength;
419  }
420 
421  assert(search_cell_index == make_index(search_index));
422  assert(search_cell_index >= 0);
423  assert(search_cell_index < SizeType(cells_.size()));
424  ParticleList search = cells_[search_cell_index];
425  search_cell_callback(search);
426 
427  auto virtual_search_index = search_index;
428  ThreeVector wrap_vector = {}; // no change
429  auto current_wrap_vector = wrap_vector;
430 
431  for (const auto &dz : dz_list) {
432  if (dz.wrap == NeedsToWrap::MinusLength) {
433  // last dz in the loop, so no need to undo the wrap
434  wrap_vector[2] = -length_[2];
435  virtual_search_index[2] = -1;
436  }
437  for (const auto &dy : dy_list) {
438  // only the last dy in dy_list can wrap
439  if (dy.wrap == NeedsToWrap::MinusLength) {
440  wrap_vector[1] = -length_[1];
441  virtual_search_index[1] = -1;
442  } else if (dy.wrap == NeedsToWrap::PlusLength) {
443  wrap_vector[1] = length_[1];
444  virtual_search_index[1] = number_of_cells_[1];
445  }
446  for (const auto &dx : dx_list) {
447  // only the last dx in dx_list can wrap
448  if (dx.wrap == NeedsToWrap::MinusLength) {
449  wrap_vector[0] = -length_[0];
450  virtual_search_index[0] = -1;
451  } else if (dx.wrap == NeedsToWrap::PlusLength) {
452  wrap_vector[0] = length_[0];
453  virtual_search_index[0] = number_of_cells_[0];
454  }
455  assert(dx.index >= 0);
456  assert(dx.index < number_of_cells_[0]);
457  assert(dy.index >= 0);
458  assert(dy.index < number_of_cells_[1]);
459  assert(dz.index >= 0);
460  assert(dz.index < number_of_cells_[2]);
461  const auto neighbor_cell_index =
462  make_index(dx.index, dy.index, dz.index);
463  assert(neighbor_cell_index >= 0);
464  assert(neighbor_cell_index < SizeType(cells_.size()));
465  if (neighbor_cell_index <= make_index(virtual_search_index)) {
466  continue;
467  }
468 
469  if (wrap_vector != current_wrap_vector) {
470  logg[LGrid].debug("translating search cell by ",
471  wrap_vector - current_wrap_vector);
472  for_each(search, [&](ParticleData &p) {
473  p = p.translated(wrap_vector - current_wrap_vector);
474  });
475  current_wrap_vector = wrap_vector;
476  }
477  neighbor_cell_callback(search, cells_[neighbor_cell_index]);
478  }
479  virtual_search_index[0] = search_index[0];
480  wrap_vector[0] = 0;
481  }
482  virtual_search_index[1] = search_index[1];
483  wrap_vector[1] = 0;
484  }
485  }
486  }
487  }
488 }
489 
491  const std::pair<std::array<double, 3>, std::array<double, 3>>
492  &min_and_length,
493  const Particles &particles, double max_interaction_length,
494  double timestep_duration, CellNumberLimitation limit,
495  const bool include_unformed_particles, CellSizeStrategy strategy);
497  const std::pair<std::array<double, 3>, std::array<double, 3>>
498  &min_and_length,
499  const Particles &particles, double max_interaction_length,
500  double timestep_duration, CellNumberLimitation limit,
501  const bool include_unformed_particles, CellSizeStrategy strategy);
502 } // namespace smash
Generic algorithms on containers and ranges.
int SizeType
A type to store the sizes.
Definition: grid.h:70
static std::pair< std::array< double, 3 >, std::array< double, 3 > > find_min_and_length(const Particles &particles)
Definition: grid.cc:87
Abstracts a list of cells that partition the particles in the experiment into regions of space that c...
Definition: grid.h:96
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:190
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:196
double cell_volume_
The volume of a single cell.
Definition: grid.h:193
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:199
Grid(const Particles &particles, double min_cell_length, double timestep_duration, CellNumberLimitation limit, const bool include_unformed_particles=false, CellSizeStrategy strategy=CellSizeStrategy::Optimal)
Constructs a grid from the given particle list particles.
Definition: grid.h:113
void iterate_cells(const std::function< void(const ParticleList &)> &search_cell_callback, const std::function< void(const ParticleList &, const ParticleList &)> &neighbor_cell_callback) const
Iterates over all cells in the grid and calls the callback arguments with a search cell and 0 to 13 n...
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:290
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:58
const FourVector & position() const
Get the particle's position in Minkowski space.
Definition: particledata.h:204
The Particles class abstracts the storage and manipulation of particles.
Definition: particles.h:33
size_t size() const
Definition: particles.h:87
ParticleList copy_to_vector() const
Definition: particles.h:44
ParticleData & front()
Definition: particles.h:374
iterator begin()
Definition: particles.h:395
iterator end()
Definition: particles.h:419
The ThreeVector class represents a physical three-vector with the components .
Definition: threevector.h:31
#define SMASH_SOURCE_LOCATION
Hackery that is required to output the location in the source code where the log statement occurs.
Definition: logging.h:153
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
#define unlikely(x)
Tell the branch predictor that this expression is likely false.
Definition: macros.h:16
constexpr int p
Proton.
constexpr int n
Neutron.
Definition: action.h:24
static const std::initializer_list< GridBase::SizeType > ZERO_ONE
Definition: grid.cc:296
UnaryFunction for_each(Container &&c, UnaryFunction &&f)
Convenience wrapper for std::for_each that operates on a complete container.
Definition: algorithms.h:96
NeedsToWrap
The options determining what to do if a particle flies out of the grids PlusLength: Used if a periodi...
Definition: grid.cc:355
CellNumberLimitation
Identifies whether the number of cells should be limited.
Definition: grid.h:55
@ ParticleNumber
Limit the number of cells to the number of particles.
bool all_of(Container &&c, UnaryPredicate &&p)
Convenience wrapper for std::all_of that operates on a complete container.
Definition: algorithms.h:80
@ Normal
Without ghost cells.
@ PeriodicBoundaries
With ghost cells for periodic boundaries.
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO
Definition: grid.cc:297
static const std::initializer_list< GridBase::SizeType > ZERO
Definition: grid.cc:295
static constexpr int LGrid
Definition: grid.cc:81
CellSizeStrategy
Indentifies the strategy of determining the cell size.
Definition: grid.h:33
@ Largest
Make cells as large as possible.
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO_ONE
Definition: grid.cc:298
A strust containing the informations needed to search the neighboring cell.
Definition: grid.cc:358
Grid< GridOptions::PeriodicBoundaries >::SizeType index
Index of the cell.
Definition: grid.cc:360
NeedsToWrap wrap
Option to determine the neighbors of the cells on the boundary.
Definition: grid.cc:362