Version: SMASH-2.1
grid.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2021
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 {
21 template <typename T>
22 static std::ostream &operator<<(std::ostream &out, const std::vector<T> &v) {
23  auto column = out.tellp();
24  out << "{ ";
25  for (const auto &x : v) {
26  if (out.tellp() - column >= 100) {
27  out << '\n';
28  column = out.tellp();
29  }
30  out << x << ' ';
31  }
32  return out << '}';
33 }
34 
35 template <typename T>
36 static std::ostream &operator<<(std::ostream &out,
37  const std::initializer_list<T> &v) {
38  auto column = out.tellp();
39  out << "{ ";
40  for (const auto &x : v) {
41  if (out.tellp() - column >= 100) {
42  out << '\n';
43  column = out.tellp();
44  }
45  out << x << ' ';
46  }
47  return out << '}';
48 }
49 
50 template <typename T, std::size_t N>
51 static std::ostream &operator<<(std::ostream &out, const std::array<T, N> &a) {
52  auto column = out.tellp();
53  out << "{ ";
54  for (const auto &x : a) {
55  if (out.tellp() - column >= 100) {
56  out << '\n';
57  column = out.tellp();
58  }
59  out << x << ' ';
60  }
61  return out << '}';
62 }
63 } // namespace std
64 
65 namespace smash {
66 static constexpr int LGrid = LogArea::Grid::id;
67 
69 // GridBase
70 
71 std::pair<std::array<double, 3>, std::array<double, 3>>
73  std::pair<std::array<double, 3>, std::array<double, 3>> r;
74  auto &min_position = r.first;
75  auto &length = r.second;
76 
77  // intialize min and max position arrays with the position of the first
78  // particle in the list
79  const auto &first_position = particles.front().position();
80  min_position = {{first_position[1], first_position[2], first_position[3]}};
81  auto max_position = min_position;
82  for (const auto &p : particles) {
83  const auto &pos = p.position();
84  min_position[0] = std::min(min_position[0], pos[1]);
85  min_position[1] = std::min(min_position[1], pos[2]);
86  min_position[2] = std::min(min_position[2], pos[3]);
87  max_position[0] = std::max(max_position[0], pos[1]);
88  max_position[1] = std::max(max_position[1], pos[2]);
89  max_position[2] = std::max(max_position[2], pos[3]);
90  }
91  length[0] = max_position[0] - min_position[0];
92  length[1] = max_position[1] - min_position[1];
93  length[2] = max_position[2] - min_position[2];
94  return r;
95 }
96 
98 // Grid
99 
100 template <GridOptions O>
101 Grid<O>::Grid(const std::pair<std::array<double, 3>, std::array<double, 3>>
102  &min_and_length,
103  const Particles &particles, double max_interaction_length,
104  double timestep_duration, CellNumberLimitation limit,
105  CellSizeStrategy strategy)
106  : length_(min_and_length.second) {
107  const auto min_position = min_and_length.first;
108  const SizeType particle_count = particles.size();
109 
110  // very simple setup for non-periodic boundaries and largest cellsize strategy
111  if (O == GridOptions::Normal && strategy == CellSizeStrategy::Largest) {
112  number_of_cells_ = {1, 1, 1};
113  cell_volume_ = length_[0] * length_[1] * length_[2];
114  cells_.clear();
115  cells_.reserve(1);
116  cells_.emplace_back(particles.copy_to_vector());
117  return;
118  }
119 
120  // The number of cells is determined by the min and max coordinates where
121  // particles are positioned and the maximal interaction length (which equals
122  // the length of a cell).
123  // But don't let the number of cells exceed the actual number of particles.
124  // That would be overkill. Let max_cells³ ≤ particle_count (conversion to
125  // int truncates). Limit only applied for geometric criteiron, where grid
126  // is an optimisation and cells can be made larger.
127  const int max_cells =
128  (O == GridOptions::Normal)
129  ? std::cbrt(particle_count)
130  : std::max(2, static_cast<int>(std::cbrt(particle_count)));
131 
132  // This normally equals 1/max_interaction_length. If the number of cells
133  // is reduced (because of low density) then this value is smaller. If only
134  // one cell is used than this value might also be larger.
135  std::array<double, 3> index_factor = {1. / max_interaction_length,
136  1. / max_interaction_length,
137  1. / max_interaction_length};
138  for (std::size_t i = 0; i < number_of_cells_.size(); ++i) {
139  number_of_cells_[i] =
140  (strategy == CellSizeStrategy::Largest)
141  ? 2
142  : static_cast<int>(std::floor(length_[i] * index_factor[i]));
143 
144  if (number_of_cells_[i] == 0) {
145  // In case of zero cells, make at least one cell that is then smaller than
146  // the minimal cell length. This is ok for all setups, since all particles
147  // are inside the same cell, except for the box with peroidic boundary
148  // conditions, where we need a 2x2x2 grid.
149  number_of_cells_[i] = 1;
150  } else if (number_of_cells_[i] < 2 &&
152  // Double the minimal cell length exceeds the length of the box, but we
153  // need at least 2x2x2 cells for periodic boundaries.
154  std::string error_box_too_small =
155  "Input error: Your box is too small for the grid.\n"
156  "The minimal length of the box is given by: " +
157  std::to_string(2 * max_interaction_length) +
158  " fm with the given timestep size.\n"
159  "If you have large timesteps please reduce them.\n"
160  "A larger box or the use of testparticles also helps.\n"
161  "Please take a look at your config.";
162  throw std::runtime_error(error_box_too_small);
163  } else if (limit == CellNumberLimitation::ParticleNumber &&
164  number_of_cells_[i] > max_cells) {
165  number_of_cells_[i] = max_cells;
166  }
167  // Only bother rescaling the index_factor if the grid length is large enough
168  // for 1 full min. cell length, since all particles are anyway placed in the
169  // first cell along the i-th axis
170  if (length_[i] >= max_interaction_length) {
171  index_factor[i] = number_of_cells_[i] / length_[i];
172  // std::nextafter implements a safety margin so that no valid position
173  // inside the grid can reference an out-of-bounds cell
174  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
175  index_factor[i] = std::nextafter(index_factor[i], 0.);
176  }
177  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
178  }
179  }
180 
181  if (O == GridOptions::Normal &&
182  all_of(number_of_cells_, [](SizeType n) { return n <= 2; })) {
183  // dilute limit:
184  // the grid would have <= 2x2x2 cells, meaning every particle has to be
185  // compared with every other particle anyway. Then we can just as well
186  // fall back to not using the grid at all
187  // For a grid with periodic boundaries the situation is different and we
188  // never want to have a grid smaller than 2x2x2.
189  logg[LGrid].debug(
190  "There would only be ", number_of_cells_,
191  " cells. Therefore the Grid falls back to a single cell / "
192  "particle list.");
193  number_of_cells_ = {1, 1, 1};
194  cell_volume_ = length_[0] * length_[1] * length_[2];
195  cells_.resize(1);
196  cells_.front().reserve(particles.size());
197  std::copy_if(particles.begin(), particles.end(),
198  std::back_inserter(cells_.front()),
199  [&](const ParticleData &p) {
200  return p.xsec_scaling_factor(timestep_duration) > 0.0;
201  }); // filter out the particles that can not interact
202  } else {
203  // construct a normal grid
204 
206  (length_[1] / number_of_cells_[1]) *
207  (length_[2] / number_of_cells_[2]);
208 
209  logg[LGrid].debug("min: ", min_position, "\nlength: ", length_,
210  "\ncell_volume: ", cell_volume_,
211  "\ncells: ", number_of_cells_,
212  "\nindex_factor: ", index_factor);
213 
214  // After the grid parameters are determined, we can start placing the
215  // particles in cells.
216  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
217  number_of_cells_[2]);
218 
219  // Returns the one-dimensional cell-index from the position vector inside
220  // the grid.
221  // This simply calculates the distance to min_position and multiplies it
222  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
223  auto &&cell_index_for = [&](const ParticleData &p) {
224  return make_index(
225  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
226  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
227  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
228  };
229 
230  for (const auto &p : particles) {
231  if (p.xsec_scaling_factor(timestep_duration) > 0.0) {
232  const auto idx = cell_index_for(p);
233 #ifndef NDEBUG
234  if (idx >= SizeType(cells_.size())) {
235  logg[LGrid].fatal(
237  "\nan out-of-bounds access would be necessary for the "
238  "particle ",
239  p, "\nfor a grid with the following parameters:\nmin: ",
240  min_position, "\nlength: ", length_,
241  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor,
242  "\ncells_.size: ", cells_.size(), "\nrequested index: ", idx);
243  throw std::runtime_error("out-of-bounds grid access on construction");
244  }
245 #endif
246  cells_[idx].push_back(p);
247  }
248  }
249  }
250 
251  logg[LGrid].debug(cells_);
252 }
253 
254 template <GridOptions Options>
256  SizeType x, SizeType y, SizeType z) const {
257  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
258 }
259 
260 static const std::initializer_list<GridBase::SizeType> ZERO{0};
261 static const std::initializer_list<GridBase::SizeType> ZERO_ONE{0, 1};
262 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO{-1, 0};
263 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO_ONE{-1, 0,
264  1};
265 
266 template <>
269  const std::function<void(const ParticleList &)> &search_cell_callback,
270  const std::function<void(const ParticleList &, const ParticleList &)>
271  &neighbor_cell_callback) const {
272  std::array<SizeType, 3> search_index;
273  SizeType &x = search_index[0];
274  SizeType &y = search_index[1];
275  SizeType &z = search_index[2];
276  SizeType search_cell_index = 0;
277  for (z = 0; z < number_of_cells_[2]; ++z) {
278  for (y = 0; y < number_of_cells_[1]; ++y) {
279  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
280  assert(search_cell_index == make_index(search_index));
281  assert(search_cell_index >= 0);
282  assert(search_cell_index < SizeType(cells_.size()));
283  const ParticleList &search = cells_[search_cell_index];
284  search_cell_callback(search);
285 
286  const auto &dz_list = z == number_of_cells_[2] - 1 ? ZERO : ZERO_ONE;
287  const auto &dy_list = number_of_cells_[1] == 1
288  ? ZERO
289  : y == 0 ? ZERO_ONE
290  : y == number_of_cells_[1] - 1
293  const auto &dx_list = number_of_cells_[0] == 1
294  ? ZERO
295  : x == 0 ? ZERO_ONE
296  : x == number_of_cells_[0] - 1
299  for (SizeType dz : dz_list) {
300  for (SizeType dy : dy_list) {
301  for (SizeType dx : dx_list) {
302  const auto di = make_index(dx, dy, dz);
303  if (di > 0) {
304  neighbor_cell_callback(search, cells_[search_cell_index + di]);
305  }
306  }
307  }
308  }
309  }
310  }
311  }
312 }
313 
323 
330 };
331 
332 template <>
335  const std::function<void(const ParticleList &)> &search_cell_callback,
336  const std::function<void(const ParticleList &, const ParticleList &)>
337  &neighbor_cell_callback) const {
338  std::array<SizeType, 3> search_index;
339  SizeType &x = search_index[0];
340  SizeType &y = search_index[1];
341  SizeType &z = search_index[2];
342  SizeType search_cell_index = 0;
343 
344  // defaults:
345  std::array<NeighborLookup, 2> dz_list;
346  std::array<NeighborLookup, 3> dy_list;
347  std::array<NeighborLookup, 3> dx_list;
348 
349  assert(number_of_cells_[2] >= 2);
350  assert(number_of_cells_[1] >= 2);
351  assert(number_of_cells_[0] >= 2);
352 
353  for (z = 0; z < number_of_cells_[2]; ++z) {
354  dz_list[0].index = z;
355  dz_list[1].index = z + 1;
356  if (dz_list[1].index == number_of_cells_[2]) {
357  dz_list[1].index = 0;
358  // last z in the loop, so no need to reset wrap again
359  dz_list[1].wrap = NeedsToWrap::MinusLength;
360  }
361  for (y = 0; y < number_of_cells_[1]; ++y) {
362  dy_list[0].index = y;
363  dy_list[1].index = y - 1;
364  dy_list[2].index = y + 1;
365  dy_list[2].wrap = NeedsToWrap::No;
366  if (y == 0) {
367  dy_list[1] = dy_list[2];
368  dy_list[2].index = number_of_cells_[1] - 1;
369  dy_list[2].wrap = NeedsToWrap::PlusLength;
370  } else if (dy_list[2].index == number_of_cells_[1]) {
371  dy_list[2].index = 0;
372  dy_list[2].wrap = NeedsToWrap::MinusLength;
373  }
374  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
375  dx_list[0].index = x;
376  dx_list[1].index = x - 1;
377  dx_list[2].index = x + 1;
378  dx_list[2].wrap = NeedsToWrap::No;
379  if (x == 0) {
380  dx_list[1] = dx_list[2];
381  dx_list[2].index = number_of_cells_[0] - 1;
382  dx_list[2].wrap = NeedsToWrap::PlusLength;
383  } else if (dx_list[2].index == number_of_cells_[0]) {
384  dx_list[2].index = 0;
385  dx_list[2].wrap = NeedsToWrap::MinusLength;
386  }
387 
388  assert(search_cell_index == make_index(search_index));
389  assert(search_cell_index >= 0);
390  assert(search_cell_index < SizeType(cells_.size()));
391  ParticleList search = cells_[search_cell_index];
392  search_cell_callback(search);
393 
394  auto virtual_search_index = search_index;
395  ThreeVector wrap_vector = {}; // no change
396  auto current_wrap_vector = wrap_vector;
397 
398  for (const auto &dz : dz_list) {
399  if (dz.wrap == NeedsToWrap::MinusLength) {
400  // last dz in the loop, so no need to undo the wrap
401  wrap_vector[2] = -length_[2];
402  virtual_search_index[2] = -1;
403  }
404  for (const auto &dy : dy_list) {
405  // only the last dy in dy_list can wrap
406  if (dy.wrap == NeedsToWrap::MinusLength) {
407  wrap_vector[1] = -length_[1];
408  virtual_search_index[1] = -1;
409  } else if (dy.wrap == NeedsToWrap::PlusLength) {
410  wrap_vector[1] = length_[1];
411  virtual_search_index[1] = number_of_cells_[1];
412  }
413  for (const auto &dx : dx_list) {
414  // only the last dx in dx_list can wrap
415  if (dx.wrap == NeedsToWrap::MinusLength) {
416  wrap_vector[0] = -length_[0];
417  virtual_search_index[0] = -1;
418  } else if (dx.wrap == NeedsToWrap::PlusLength) {
419  wrap_vector[0] = length_[0];
420  virtual_search_index[0] = number_of_cells_[0];
421  }
422  assert(dx.index >= 0);
423  assert(dx.index < number_of_cells_[0]);
424  assert(dy.index >= 0);
425  assert(dy.index < number_of_cells_[1]);
426  assert(dz.index >= 0);
427  assert(dz.index < number_of_cells_[2]);
428  const auto neighbor_cell_index =
429  make_index(dx.index, dy.index, dz.index);
430  assert(neighbor_cell_index >= 0);
431  assert(neighbor_cell_index < SizeType(cells_.size()));
432  if (neighbor_cell_index <= make_index(virtual_search_index)) {
433  continue;
434  }
435 
436  if (wrap_vector != current_wrap_vector) {
437  logg[LGrid].debug("translating search cell by ",
438  wrap_vector - current_wrap_vector);
439  for_each(search, [&](ParticleData &p) {
440  p = p.translated(wrap_vector - current_wrap_vector);
441  });
442  current_wrap_vector = wrap_vector;
443  }
444  neighbor_cell_callback(search, cells_[neighbor_cell_index]);
445  }
446  virtual_search_index[0] = search_index[0];
447  wrap_vector[0] = 0;
448  }
449  virtual_search_index[1] = search_index[1];
450  wrap_vector[1] = 0;
451  }
452  }
453  }
454  }
455 }
456 
458  const std::pair<std::array<double, 3>, std::array<double, 3>>
459  &min_and_length,
460  const Particles &particles, double max_interaction_length,
461  double timestep_duration, CellNumberLimitation limit,
462  CellSizeStrategy strategy);
464  const std::pair<std::array<double, 3>, std::array<double, 3>>
465  &min_and_length,
466  const Particles &particles, double max_interaction_length,
467  double timestep_duration, CellNumberLimitation limit,
468  CellSizeStrategy strategy);
469 } // 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:72
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:183
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:189
double cell_volume_
The volume of a single cell.
Definition: grid.h:186
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:192
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...
Grid(const Particles &particles, double min_cell_length, double timestep_duration, CellNumberLimitation limit, CellSizeStrategy strategy=CellSizeStrategy::Optimal)
Constructs a grid from the given particle list particles.
Definition: grid.h:111
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:255
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:359
iterator begin()
Definition: particles.h:380
iterator end()
Definition: particles.h:404
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:243
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
constexpr int p
Proton.
constexpr int n
Neutron.
Definition: action.h:24
static const std::initializer_list< GridBase::SizeType > ZERO_ONE
Definition: grid.cc:261
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:322
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:262
static const std::initializer_list< GridBase::SizeType > ZERO
Definition: grid.cc:260
static constexpr int LGrid
Definition: grid.cc:66
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:263
A strust containing the informations needed to search the neighboring cell.
Definition: grid.cc:325
Grid< GridOptions::PeriodicBoundaries >::SizeType index
Index of the cell.
Definition: grid.cc:327
NeedsToWrap wrap
Option to determine the neighbors of the cells on the boundary.
Definition: grid.cc:329