Version: SMASH-2.0
grid.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2020
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, CellSizeStrategy strategy)
105  : length_(min_and_length.second) {
106  const auto min_position = min_and_length.first;
107  const SizeType particle_count = particles.size();
108 
109  // very simple setup for non-periodic boundaries and largest cellsize strategy
110  if (O == GridOptions::Normal && strategy == CellSizeStrategy::Largest) {
111  number_of_cells_ = {1, 1, 1};
112  cell_volume_ = length_[0] * length_[1] * length_[2];
113  cells_.clear();
114  cells_.reserve(1);
115  cells_.emplace_back(particles.copy_to_vector());
116  return;
117  }
118 
119  // The number of cells is determined by the min and max coordinates where
120  // particles are positioned and the maximal interaction length (which equals
121  // the length of a cell).
122  // But don't let the number of cells exceed the actual number of particles.
123  // That would be overkill. Let max_cells³ ≤ particle_count (conversion to
124  // int truncates).
125  // Consider that particle placement into cells uses half-open intervals. Thus
126  // a cell includes particles in [0, a[. The next cell [a, 2a[. And so on. This
127  // is important for calculating the number of cells. If length * index_factor
128  // (equivalent to length / max_interaction_length) is integral, then
129  // length * index_factor + 1 determines the number of required cells. That's
130  // because the last cell will then store particles in the interval
131  // [length, length + max_interaction_length[. The code below achieves this
132  // effect by rounding down (floor) and adding 1 afterwards.
133  const int max_cells =
134  (O == GridOptions::Normal)
135  ? std::cbrt(particle_count)
136  : std::max(2, static_cast<int>(std::cbrt(particle_count)));
137 
138  std::string error_box_too_small =
139  "Input error: Your box is too small for the grid.\n"
140  "The minimal length of the box is given by: " +
141  std::to_string(2 * max_interaction_length) +
142  " fm with the given timestep size.\n"
143  "If you have large timesteps please reduce them.\n"
144  "A larger box or the use of testparticles also helps.\n"
145  "Please take a look at your config.";
146 
147  // This normally equals 1/max_interaction_length, but if the number of cells
148  // is reduced (because of low density) then this value is smaller.
149  std::array<double, 3> index_factor = {1. / max_interaction_length,
150  1. / max_interaction_length,
151  1. / max_interaction_length};
152  for (std::size_t i = 0; i < number_of_cells_.size(); ++i) {
153  number_of_cells_[i] =
154  (strategy == CellSizeStrategy::Largest)
155  ? 2
156  : static_cast<int>(std::floor(length_[i] * index_factor[i])) +
157  // The last cell in each direction can be smaller than
158  // max_interaction_length. In that case periodic boundaries
159  // will not work correctly. Thus, we need to reduce the number
160  // of cells in that direction by one and make the last cell
161  // larger. This basically merges a smaller boundary cell into
162  // a full cell inside the grid. There's a ~0% chance that the
163  // given boundaries create an integral number of cells with
164  // length of max_interaction_length. Therefore, just make the
165  // default number of cells one less than for non-periodic
166  // boundaries.
167  (O == GridOptions::Normal ? 1 : 0);
168  // Only in the case of periodic boundaries (i.e. GridOptions != Normal) the
169  // number of cells can be zero.
170  if (number_of_cells_[i] == 0) {
171  // The minimal cell length exceeds the length of the box.
172  throw std::runtime_error(error_box_too_small);
173  }
174  // std::nextafter implements a safety margin so that no valid position
175  // inside the grid can reference an out-of-bounds cell
176  if (number_of_cells_[i] > max_cells) {
177  number_of_cells_[i] = max_cells;
178  index_factor[i] = number_of_cells_[i] / length_[i];
179  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
180  index_factor[i] = std::nextafter(index_factor[i], 0.);
181  }
182  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
183  } else if (O == GridOptions::PeriodicBoundaries) {
184  if (number_of_cells_[i] == 1) {
185  number_of_cells_[i] = 2;
186  }
187  index_factor[i] = number_of_cells_[i] / length_[i];
188  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
189  index_factor[i] = std::nextafter(index_factor[i], 0.);
190  }
191  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
192  // Verify that cell length did not become smaller than
193  // the max. interaction length by increasing the number of cells
194  if (1. / index_factor[i] <= std::nextafter(max_interaction_length, 0.)) {
195  // The minimal cell length exceeds
196  // the length of a grid cell in the box.
197  throw std::runtime_error(error_box_too_small);
198  }
199  }
200  }
201 
202  cell_volume_ = (length_[0] / number_of_cells_[0]) *
203  (length_[1] / number_of_cells_[1]) *
204  (length_[2] / number_of_cells_[2]);
205 
206  if (O == GridOptions::Normal &&
207  all_of(number_of_cells_, [](SizeType n) { return n <= 2; })) {
208  // dilute limit:
209  // the grid would have <= 2x2x2 cells, meaning every particle has to be
210  // compared with every other particle anyway. Then we can just as well
211  // fall back to not using the grid at all
212  // For a grid with periodic boundaries the situation is different and we
213  // never want to have a grid smaller than 2x2x2.
214  logg[LGrid].debug(
215  "There would only be ", number_of_cells_,
216  " cells. Therefore the Grid falls back to a single cell / "
217  "particle list.");
218  number_of_cells_ = {1, 1, 1};
219  cell_volume_ = length_[0] * length_[1] * length_[2];
220  cells_.resize(1);
221  cells_.front().reserve(particles.size());
222  std::copy_if(particles.begin(), particles.end(),
223  std::back_inserter(cells_.front()),
224  [&](const ParticleData &p) {
225  return p.xsec_scaling_factor(timestep_duration) > 0.0;
226  }); // filter out the particles that can not interact
227  } else {
228  // construct a normal grid
229  logg[LGrid].debug("min: ", min_position, "\nlength: ", length_,
230  "\ncell_volume: ", cell_volume_,
231  "\ncells: ", number_of_cells_,
232  "\nindex_factor: ", index_factor);
233 
234  // After the grid parameters are determined, we can start placing the
235  // particles in cells.
236  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
237  number_of_cells_[2]);
238 
239  // Returns the one-dimensional cell-index from the position vector inside
240  // the grid.
241  // This simply calculates the distance to min_position and multiplies it
242  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
243  auto &&cell_index_for = [&](const ParticleData &p) {
244  return make_index(
245  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
246  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
247  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
248  };
249 
250  for (const auto &p : particles) {
251  if (p.xsec_scaling_factor(timestep_duration) > 0.0) {
252  const auto idx = cell_index_for(p);
253 #ifndef NDEBUG
254  if (idx >= SizeType(cells_.size())) {
255  logg[LGrid].fatal(
257  "\nan out-of-bounds access would be necessary for the "
258  "particle ",
259  p, "\nfor a grid with the following parameters:\nmin: ",
260  min_position, "\nlength: ", length_,
261  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor,
262  "\ncells_.size: ", cells_.size(), "\nrequested index: ", idx);
263  throw std::runtime_error("out-of-bounds grid access on construction");
264  }
265 #endif
266  cells_[idx].push_back(p);
267  }
268  }
269  }
270 
271  logg[LGrid].debug(cells_);
272 }
273 
274 template <GridOptions Options>
276  SizeType x, SizeType y, SizeType z) const {
277  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
278 }
279 
280 static const std::initializer_list<GridBase::SizeType> ZERO{0};
281 static const std::initializer_list<GridBase::SizeType> ZERO_ONE{0, 1};
282 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO{-1, 0};
283 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO_ONE{-1, 0,
284  1};
285 
286 template <>
289  const std::function<void(const ParticleList &)> &search_cell_callback,
290  const std::function<void(const ParticleList &, const ParticleList &)>
291  &neighbor_cell_callback) const {
292  std::array<SizeType, 3> search_index;
293  SizeType &x = search_index[0];
294  SizeType &y = search_index[1];
295  SizeType &z = search_index[2];
296  SizeType search_cell_index = 0;
297  for (z = 0; z < number_of_cells_[2]; ++z) {
298  for (y = 0; y < number_of_cells_[1]; ++y) {
299  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
300  assert(search_cell_index == make_index(search_index));
301  assert(search_cell_index >= 0);
302  assert(search_cell_index < SizeType(cells_.size()));
303  const ParticleList &search = cells_[search_cell_index];
304  search_cell_callback(search);
305 
306  const auto &dz_list = z == number_of_cells_[2] - 1 ? ZERO : ZERO_ONE;
307  const auto &dy_list = number_of_cells_[1] == 1
308  ? ZERO
309  : y == 0 ? ZERO_ONE
310  : y == number_of_cells_[1] - 1
313  const auto &dx_list = number_of_cells_[0] == 1
314  ? ZERO
315  : x == 0 ? ZERO_ONE
316  : x == number_of_cells_[0] - 1
319  for (SizeType dz : dz_list) {
320  for (SizeType dy : dy_list) {
321  for (SizeType dx : dx_list) {
322  const auto di = make_index(dx, dy, dz);
323  if (di > 0) {
324  neighbor_cell_callback(search, cells_[search_cell_index + di]);
325  }
326  }
327  }
328  }
329  }
330  }
331  }
332 }
333 
343 
349  NeedsToWrap wrap = NeedsToWrap::No;
350 };
351 
352 template <>
355  const std::function<void(const ParticleList &)> &search_cell_callback,
356  const std::function<void(const ParticleList &, const ParticleList &)>
357  &neighbor_cell_callback) const {
358  std::array<SizeType, 3> search_index;
359  SizeType &x = search_index[0];
360  SizeType &y = search_index[1];
361  SizeType &z = search_index[2];
362  SizeType search_cell_index = 0;
363 
364  // defaults:
365  std::array<NeighborLookup, 2> dz_list;
366  std::array<NeighborLookup, 3> dy_list;
367  std::array<NeighborLookup, 3> dx_list;
368 
369  assert(number_of_cells_[2] >= 2);
370  assert(number_of_cells_[1] >= 2);
371  assert(number_of_cells_[0] >= 2);
372 
373  for (z = 0; z < number_of_cells_[2]; ++z) {
374  dz_list[0].index = z;
375  dz_list[1].index = z + 1;
376  if (dz_list[1].index == number_of_cells_[2]) {
377  dz_list[1].index = 0;
378  // last z in the loop, so no need to reset wrap again
379  dz_list[1].wrap = NeedsToWrap::MinusLength;
380  }
381  for (y = 0; y < number_of_cells_[1]; ++y) {
382  dy_list[0].index = y;
383  dy_list[1].index = y - 1;
384  dy_list[2].index = y + 1;
385  dy_list[2].wrap = NeedsToWrap::No;
386  if (y == 0) {
387  dy_list[1] = dy_list[2];
388  dy_list[2].index = number_of_cells_[1] - 1;
389  dy_list[2].wrap = NeedsToWrap::PlusLength;
390  } else if (dy_list[2].index == number_of_cells_[1]) {
391  dy_list[2].index = 0;
392  dy_list[2].wrap = NeedsToWrap::MinusLength;
393  }
394  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
395  dx_list[0].index = x;
396  dx_list[1].index = x - 1;
397  dx_list[2].index = x + 1;
398  dx_list[2].wrap = NeedsToWrap::No;
399  if (x == 0) {
400  dx_list[1] = dx_list[2];
401  dx_list[2].index = number_of_cells_[0] - 1;
402  dx_list[2].wrap = NeedsToWrap::PlusLength;
403  } else if (dx_list[2].index == number_of_cells_[0]) {
404  dx_list[2].index = 0;
405  dx_list[2].wrap = NeedsToWrap::MinusLength;
406  }
407 
408  assert(search_cell_index == make_index(search_index));
409  assert(search_cell_index >= 0);
410  assert(search_cell_index < SizeType(cells_.size()));
411  ParticleList search = cells_[search_cell_index];
412  search_cell_callback(search);
413 
414  auto virtual_search_index = search_index;
415  ThreeVector wrap_vector = {}; // no change
416  auto current_wrap_vector = wrap_vector;
417 
418  for (const auto &dz : dz_list) {
419  if (dz.wrap == NeedsToWrap::MinusLength) {
420  // last dz in the loop, so no need to undo the wrap
421  wrap_vector[2] = -length_[2];
422  virtual_search_index[2] = -1;
423  }
424  for (const auto &dy : dy_list) {
425  // only the last dy in dy_list can wrap
426  if (dy.wrap == NeedsToWrap::MinusLength) {
427  wrap_vector[1] = -length_[1];
428  virtual_search_index[1] = -1;
429  } else if (dy.wrap == NeedsToWrap::PlusLength) {
430  wrap_vector[1] = length_[1];
431  virtual_search_index[1] = number_of_cells_[1];
432  }
433  for (const auto &dx : dx_list) {
434  // only the last dx in dx_list can wrap
435  if (dx.wrap == NeedsToWrap::MinusLength) {
436  wrap_vector[0] = -length_[0];
437  virtual_search_index[0] = -1;
438  } else if (dx.wrap == NeedsToWrap::PlusLength) {
439  wrap_vector[0] = length_[0];
440  virtual_search_index[0] = number_of_cells_[0];
441  }
442  assert(dx.index >= 0);
443  assert(dx.index < number_of_cells_[0]);
444  assert(dy.index >= 0);
445  assert(dy.index < number_of_cells_[1]);
446  assert(dz.index >= 0);
447  assert(dz.index < number_of_cells_[2]);
448  const auto neighbor_cell_index =
449  make_index(dx.index, dy.index, dz.index);
450  assert(neighbor_cell_index >= 0);
451  assert(neighbor_cell_index < SizeType(cells_.size()));
452  if (neighbor_cell_index <= make_index(virtual_search_index)) {
453  continue;
454  }
455 
456  if (wrap_vector != current_wrap_vector) {
457  logg[LGrid].debug("translating search cell by ",
458  wrap_vector - current_wrap_vector);
459  for_each(search, [&](ParticleData &p) {
460  p = p.translated(wrap_vector - current_wrap_vector);
461  });
462  current_wrap_vector = wrap_vector;
463  }
464  neighbor_cell_callback(search, cells_[neighbor_cell_index]);
465  }
466  virtual_search_index[0] = search_index[0];
467  wrap_vector[0] = 0;
468  }
469  virtual_search_index[1] = search_index[1];
470  wrap_vector[1] = 0;
471  }
472  }
473  }
474  }
475 }
476 
478  const std::pair<std::array<double, 3>, std::array<double, 3>>
479  &min_and_length,
480  const Particles &particles, double max_interaction_length,
481  double timestep_duration, CellSizeStrategy strategy);
483  const std::pair<std::array<double, 3>, std::array<double, 3>>
484  &min_and_length,
485  const Particles &particles, double max_interaction_length,
486  double timestep_duration, CellSizeStrategy strategy);
487 } // namespace smash
smash
Definition: action.h:24
smash::MINUS_ONE_ZERO_ONE
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO_ONE
Definition: grid.cc:283
particledata.h
algorithms.h
smash::for_each
UnaryFunction for_each(Container &&c, UnaryFunction &&f)
Convenience wrapper for std::for_each that operates on a complete container.
Definition: algorithms.h:96
smash::Particles::size
size_t size() const
Definition: particles.h:87
smash::MINUS_ONE_ZERO
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO
Definition: grid.cc:282
smash::ParticleData
Definition: particledata.h:52
smash::Particles::begin
iterator begin()
Definition: particles.h:380
grid.h
fourvector.h
smash::CellSizeStrategy
CellSizeStrategy
Indentifies the strategy of determining the cell size.
Definition: grid.h:33
smash::logg
std::array< einhard::Logger<>, std::tuple_size< LogArea::AreaTuple >::value > logg
An array that stores all pre-configured Logger objects.
Definition: logging.cc:39
smash::all_of
bool all_of(Container &&c, UnaryPredicate &&p)
Convenience wrapper for std::all_of that operates on a complete container.
Definition: algorithms.h:80
smash::Particles::end
iterator end()
Definition: particles.h:404
smash::ThreeVector
Definition: threevector.h:31
source_location
#define source_location
Hackery that is required to output the location in the source code where the log statement occurs.
Definition: logging.h:243
OutputOnlyFinal::No
Print initial, intermediate and final-state particles.
smash::Particles::copy_to_vector
ParticleList copy_to_vector() const
Definition: particles.h:44
smash::LGrid
static constexpr int LGrid
Definition: grid.cc:66
smash::ZERO
static const std::initializer_list< GridBase::SizeType > ZERO
Definition: grid.cc:280
smash::ParticleData::position
const FourVector & position() const
Get the particle's position in Minkowski space.
Definition: particledata.h:198
threevector.h
smash::Grid
Abstracts a list of cells that partition the particles in the experiment into regions of space that c...
Definition: grid.h:79
smash::NeedsToWrap::PlusLength
smash::GridBase::SizeType
int SizeType
A type to store the sizes.
Definition: grid.h:53
smash::NeighborLookup
A strust containing the informations needed to search the neighboring cell.
Definition: grid.cc:345
smash::Particles
Definition: particles.h:33
smash::NeedsToWrap
NeedsToWrap
The options determining what to do if a particle flies out of the grids PlusLength: Used if a periodi...
Definition: grid.cc:342
smash::ZERO_ONE
static const std::initializer_list< GridBase::SizeType > ZERO_ONE
Definition: grid.cc:281
logging.h
smash::Grid::Grid
Grid(const Particles &particles, double min_cell_length, double timestep_duration, CellSizeStrategy strategy=CellSizeStrategy::Optimal)
Constructs a grid from the given particle list particles.
Definition: grid.h:93
smash::Particles::front
ParticleData & front()
Definition: particles.h:359
smash::pdg::p
constexpr int p
Proton.
Definition: pdgcode_constants.h:28
smash::pdg::n
constexpr int n
Neutron.
Definition: pdgcode_constants.h:30
smash::GridBase::find_min_and_length
static std::pair< std::array< double, 3 >, std::array< double, 3 > > find_min_and_length(const Particles &particles)
Definition: grid.cc:72
smash::NeedsToWrap::MinusLength