Version: SMASH-1.6
grid.cc
Go to the documentation of this file.
1 /*
2  *
3  * Copyright (c) 2014-2019
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 
68 // GridBase
69 
70 std::pair<std::array<double, 3>, std::array<double, 3>>
71 GridBase::find_min_and_length(const Particles &particles) {
72  std::pair<std::array<double, 3>, std::array<double, 3>> r;
73  auto &min_position = r.first;
74  auto &length = r.second;
75 
76  // intialize min and max position arrays with the position of the first
77  // particle in the list
78  const auto &first_position = particles.front().position();
79  min_position = {{first_position[1], first_position[2], first_position[3]}};
80  auto max_position = min_position;
81  for (const auto &p : particles) {
82  const auto &pos = p.position();
83  min_position[0] = std::min(min_position[0], pos[1]);
84  min_position[1] = std::min(min_position[1], pos[2]);
85  min_position[2] = std::min(min_position[2], pos[3]);
86  max_position[0] = std::max(max_position[0], pos[1]);
87  max_position[1] = std::max(max_position[1], pos[2]);
88  max_position[2] = std::max(max_position[2], pos[3]);
89  }
90  length[0] = max_position[0] - min_position[0];
91  length[1] = max_position[1] - min_position[1];
92  length[2] = max_position[2] - min_position[2];
93  return r;
94 }
95 
97 // Grid
98 
99 template <GridOptions O>
100 Grid<O>::Grid(const std::pair<std::array<double, 3>, std::array<double, 3>>
101  &min_and_length,
102  const Particles &particles, double max_interaction_length,
103  double timestep_duration, CellSizeStrategy strategy)
104  : length_(min_and_length.second) {
105  const auto min_position = min_and_length.first;
106  const SizeType particle_count = particles.size();
107 
108  // very simple setup for non-periodic boundaries and largest cellsize strategy
109  if (O == GridOptions::Normal && strategy == CellSizeStrategy::Largest) {
110  number_of_cells_ = {1, 1, 1};
111  cells_.clear();
112  cells_.reserve(1);
113  cells_.emplace_back(particles.copy_to_vector());
114  return;
115  }
116 
117  // The number of cells is determined by the min and max coordinates where
118  // particles are positioned and the maximal interaction length (which equals
119  // the length of a cell).
120  // But don't let the number of cells exceed the actual number of particles.
121  // That would be overkill. Let max_cells³ ≤ particle_count (conversion to
122  // int truncates).
123  // Consider that particle placement into cells uses half-open intervals. Thus
124  // a cell includes particles in [0, a[. The next cell [a, 2a[. And so on. This
125  // is important for calculating the number of cells. If length * index_factor
126  // (equivalent to length / max_interaction_length) is integral, then
127  // length * index_factor + 1 determines the number of required cells. That's
128  // because the last cell will then store particles in the interval
129  // [length, length + max_interaction_length[. The code below achieves this
130  // effect by rounding down (floor) and adding 1 afterwards.
131  const int max_cells =
132  (O == GridOptions::Normal)
133  ? std::cbrt(particle_count)
134  : std::max(2, static_cast<int>(std::cbrt(particle_count)));
135 
136  // This normally equals 1/max_interaction_length, but if the number of cells
137  // is reduced (because of low density) then this value is smaller.
138  std::array<double, 3> index_factor = {1. / max_interaction_length,
139  1. / max_interaction_length,
140  1. / max_interaction_length};
141  for (std::size_t i = 0; i < number_of_cells_.size(); ++i) {
142  number_of_cells_[i] =
143  (strategy == CellSizeStrategy::Largest)
144  ? 2
145  : static_cast<int>(std::floor(length_[i] * index_factor[i])) +
146  // The last cell in each direction can be smaller than
147  // max_interaction_length. In that case periodic boundaries
148  // will not work correctly. Thus, we need to reduce the number
149  // of cells in that direction by one and make the last cell
150  // larger. This basically merges a smaller boundary cell into
151  // a full cell inside the grid. There's a ~0% chance that the
152  // given boundaries create an integral number of cells with
153  // length of max_interaction_length. Therefore, just make the
154  // default number of cells one less than for non-periodic
155  // boundaries.
156  (O == GridOptions::Normal ? 1 : 0);
157 
158  // std::nextafter implements a safety margin so that no valid position
159  // inside the grid can reference an out-of-bounds cell
160  if (number_of_cells_[i] > max_cells) {
161  number_of_cells_[i] = max_cells;
162  index_factor[i] = number_of_cells_[i] / length_[i];
163  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
164  index_factor[i] = std::nextafter(index_factor[i], 0.);
165  }
166  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
167  } else if (O == GridOptions::PeriodicBoundaries) {
168  if (number_of_cells_[i] == 1) {
169  number_of_cells_[i] = 2;
170  }
171  index_factor[i] = number_of_cells_[i] / length_[i];
172  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
173  index_factor[i] = std::nextafter(index_factor[i], 0.);
174  }
175  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
176  }
177  }
178 
179  const auto &log = logger<LogArea::Grid>();
180  if (O == GridOptions::Normal &&
181  all_of(number_of_cells_, [](SizeType n) { return n <= 2; })) {
182  // dilute limit:
183  // the grid would have <= 2x2x2 cells, meaning every particle has to be
184  // compared with every other particle anyway. Then we can just as well
185  // fall back to not using the grid at all
186  // For a grid with periodic boundaries the situation is different and we
187  // never want to have a grid smaller than 2x2x2.
188  log.debug("There would only be ", number_of_cells_,
189  " cells. Therefore the Grid falls back to a single cell / "
190  "particle list.");
191  number_of_cells_ = {1, 1, 1};
192  cells_.resize(1);
193  cells_.front().reserve(particles.size());
194  std::copy_if(particles.begin(), particles.end(),
195  std::back_inserter(cells_.front()),
196  [&](const ParticleData &p) {
197  return p.xsec_scaling_factor(timestep_duration) > 0.0;
198  }); // filter out the particles that can not interact
199  } else {
200  // construct a normal grid
201  log.debug("min: ", min_position, "\nlength: ", length_,
202  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor);
203 
204  // After the grid parameters are determined, we can start placing the
205  // particles in cells.
206  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
207  number_of_cells_[2]);
208 
209  // Returns the one-dimensional cell-index from the position vector inside
210  // the grid.
211  // This simply calculates the distance to min_position and multiplies it
212  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
213  auto &&cell_index_for = [&](const ParticleData &p) {
214  return make_index(
215  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
216  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
217  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
218  };
219 
220  for (const auto &p : particles) {
221  if (p.xsec_scaling_factor(timestep_duration) > 0.0) {
222  const auto idx = cell_index_for(p);
223 #ifndef NDEBUG
224  if (idx >= SizeType(cells_.size())) {
225  log.fatal(
227  "\nan out-of-bounds access would be necessary for the "
228  "particle ",
229  p, "\nfor a grid with the following parameters:\nmin: ",
230  min_position, "\nlength: ", length_,
231  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor,
232  "\ncells_.size: ", cells_.size(), "\nrequested index: ", idx);
233  throw std::runtime_error("out-of-bounds grid access on construction");
234  }
235 #endif
236  cells_[idx].push_back(p);
237  }
238  }
239  }
240 
241  log.debug(cells_);
242 }
243 
244 template <GridOptions Options>
246  SizeType x, SizeType y, SizeType z) const {
247  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
248 }
249 
250 static const std::initializer_list<GridBase::SizeType> ZERO{0};
251 static const std::initializer_list<GridBase::SizeType> ZERO_ONE{0, 1};
252 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO{-1, 0};
253 static const std::initializer_list<GridBase::SizeType> MINUS_ONE_ZERO_ONE{-1, 0,
254  1};
255 
256 template <>
259  const std::function<void(const ParticleList &)> &search_cell_callback,
260  const std::function<void(const ParticleList &, const ParticleList &)>
261  &neighbor_cell_callback) const {
262  std::array<SizeType, 3> search_index;
263  SizeType &x = search_index[0];
264  SizeType &y = search_index[1];
265  SizeType &z = search_index[2];
266  SizeType search_cell_index = 0;
267  for (z = 0; z < number_of_cells_[2]; ++z) {
268  for (y = 0; y < number_of_cells_[1]; ++y) {
269  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
270  assert(search_cell_index == make_index(search_index));
271  assert(search_cell_index >= 0);
272  assert(search_cell_index < SizeType(cells_.size()));
273  const ParticleList &search = cells_[search_cell_index];
274  search_cell_callback(search);
275 
276  const auto &dz_list = z == number_of_cells_[2] - 1 ? ZERO : ZERO_ONE;
277  const auto &dy_list = number_of_cells_[1] == 1
278  ? ZERO
279  : y == 0 ? ZERO_ONE
280  : y == number_of_cells_[1] - 1
283  const auto &dx_list = number_of_cells_[0] == 1
284  ? ZERO
285  : x == 0 ? ZERO_ONE
286  : x == number_of_cells_[0] - 1
289  for (SizeType dz : dz_list) {
290  for (SizeType dy : dy_list) {
291  for (SizeType dx : dx_list) {
292  const auto di = make_index(dx, dy, dz);
293  if (di > 0) {
294  neighbor_cell_callback(search, cells_[search_cell_index + di]);
295  }
296  }
297  }
298  }
299  }
300  }
301  }
302 }
303 
313 
320 };
321 
322 template <>
325  const std::function<void(const ParticleList &)> &search_cell_callback,
326  const std::function<void(const ParticleList &, const ParticleList &)>
327  &neighbor_cell_callback) const {
328  const auto &log = logger<LogArea::Grid>();
329 
330  std::array<SizeType, 3> search_index;
331  SizeType &x = search_index[0];
332  SizeType &y = search_index[1];
333  SizeType &z = search_index[2];
334  SizeType search_cell_index = 0;
335 
336  // defaults:
337  std::array<NeighborLookup, 2> dz_list;
338  std::array<NeighborLookup, 3> dy_list;
339  std::array<NeighborLookup, 3> dx_list;
340 
341  assert(number_of_cells_[2] >= 2);
342  assert(number_of_cells_[1] >= 2);
343  assert(number_of_cells_[0] >= 2);
344 
345  for (z = 0; z < number_of_cells_[2]; ++z) {
346  dz_list[0].index = z;
347  dz_list[1].index = z + 1;
348  if (dz_list[1].index == number_of_cells_[2]) {
349  dz_list[1].index = 0;
350  // last z in the loop, so no need to reset wrap again
351  dz_list[1].wrap = NeedsToWrap::MinusLength;
352  }
353  for (y = 0; y < number_of_cells_[1]; ++y) {
354  dy_list[0].index = y;
355  dy_list[1].index = y - 1;
356  dy_list[2].index = y + 1;
357  dy_list[2].wrap = NeedsToWrap::No;
358  if (y == 0) {
359  dy_list[1] = dy_list[2];
360  dy_list[2].index = number_of_cells_[1] - 1;
361  dy_list[2].wrap = NeedsToWrap::PlusLength;
362  } else if (dy_list[2].index == number_of_cells_[1]) {
363  dy_list[2].index = 0;
364  dy_list[2].wrap = NeedsToWrap::MinusLength;
365  }
366  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
367  dx_list[0].index = x;
368  dx_list[1].index = x - 1;
369  dx_list[2].index = x + 1;
370  dx_list[2].wrap = NeedsToWrap::No;
371  if (x == 0) {
372  dx_list[1] = dx_list[2];
373  dx_list[2].index = number_of_cells_[0] - 1;
374  dx_list[2].wrap = NeedsToWrap::PlusLength;
375  } else if (dx_list[2].index == number_of_cells_[0]) {
376  dx_list[2].index = 0;
377  dx_list[2].wrap = NeedsToWrap::MinusLength;
378  }
379 
380  assert(search_cell_index == make_index(search_index));
381  assert(search_cell_index >= 0);
382  assert(search_cell_index < SizeType(cells_.size()));
383  ParticleList search = cells_[search_cell_index];
384  search_cell_callback(search);
385 
386  auto virtual_search_index = search_index;
387  ThreeVector wrap_vector = {}; // no change
388  auto current_wrap_vector = wrap_vector;
389 
390  for (const auto &dz : dz_list) {
391  if (dz.wrap == NeedsToWrap::MinusLength) {
392  // last dz in the loop, so no need to undo the wrap
393  wrap_vector[2] = -length_[2];
394  virtual_search_index[2] = -1;
395  }
396  for (const auto &dy : dy_list) {
397  // only the last dy in dy_list can wrap
398  if (dy.wrap == NeedsToWrap::MinusLength) {
399  wrap_vector[1] = -length_[1];
400  virtual_search_index[1] = -1;
401  } else if (dy.wrap == NeedsToWrap::PlusLength) {
402  wrap_vector[1] = length_[1];
403  virtual_search_index[1] = number_of_cells_[1];
404  }
405  for (const auto &dx : dx_list) {
406  // only the last dx in dx_list can wrap
407  if (dx.wrap == NeedsToWrap::MinusLength) {
408  wrap_vector[0] = -length_[0];
409  virtual_search_index[0] = -1;
410  } else if (dx.wrap == NeedsToWrap::PlusLength) {
411  wrap_vector[0] = length_[0];
412  virtual_search_index[0] = number_of_cells_[0];
413  }
414  assert(dx.index >= 0);
415  assert(dx.index < number_of_cells_[0]);
416  assert(dy.index >= 0);
417  assert(dy.index < number_of_cells_[1]);
418  assert(dz.index >= 0);
419  assert(dz.index < number_of_cells_[2]);
420  const auto neighbor_cell_index =
421  make_index(dx.index, dy.index, dz.index);
422  assert(neighbor_cell_index >= 0);
423  assert(neighbor_cell_index < SizeType(cells_.size()));
424  if (neighbor_cell_index <= make_index(virtual_search_index)) {
425  continue;
426  }
427 
428  if (wrap_vector != current_wrap_vector) {
429  log.debug("translating search cell by ",
430  wrap_vector - current_wrap_vector);
431  for_each(search, [&](ParticleData &p) {
432  p = p.translated(wrap_vector - current_wrap_vector);
433  });
434  current_wrap_vector = wrap_vector;
435  }
436  neighbor_cell_callback(search, cells_[neighbor_cell_index]);
437  }
438  virtual_search_index[0] = search_index[0];
439  wrap_vector[0] = 0;
440  }
441  virtual_search_index[1] = search_index[1];
442  wrap_vector[1] = 0;
443  }
444  }
445  }
446  }
447 }
448 
450  const std::pair<std::array<double, 3>, std::array<double, 3>>
451  &min_and_length,
452  const Particles &particles, double max_interaction_length,
453  double timestep_duration, CellSizeStrategy strategy);
455  const std::pair<std::array<double, 3>, std::array<double, 3>>
456  &min_and_length,
457  const Particles &particles, double max_interaction_length,
458  double timestep_duration, CellSizeStrategy strategy);
459 } // namespace smash
NeedsToWrap
The options determining what to do if a particle flies out of the grids PlusLength: Used if a periodi...
Definition: grid.cc:312
ParticleData & front()
Definition: particles.h:359
ParticleData translated(const ThreeVector &delta) const
Translate the particle position.
Definition: particledata.h:205
With ghost cells for periodic boundaries.
The ThreeVector class represents a physical three-vector with the components .
Definition: threevector.h:30
Without ghost cells.
const FourVector & position() const
Get the particle&#39;s position in Minkowski space.
Definition: particledata.h:185
iterator begin()
Definition: particles.h:380
static const std::initializer_list< GridBase::SizeType > ZERO_ONE
Definition: grid.cc:251
STL namespace.
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:160
ParticleList copy_to_vector() const
Definition: particles.h:44
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
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...
#define source_location
Hackery that is required to output the location in the source code where the log statement occurs...
Definition: logging.h:246
double xsec_scaling_factor(double delta_time=0.) const
Return the cross section scaling factor at a given time.
Definition: particledata.cc:71
Generic algorithms on containers and ranges.
size_t size() const
Definition: particles.h:87
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:157
int SizeType
A type to store the sizes.
Definition: grid.h:53
bool all_of(Container &&c, UnaryPredicate &&p)
Convenience wrapper for std::all_of that operates on a complete container.
Definition: algorithms.h:80
UnaryFunction for_each(Container &&c, UnaryFunction &&f)
Convenience wrapper for std::for_each that operates on a complete container.
Definition: algorithms.h:96
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO
Definition: grid.cc:252
iterator end()
Definition: particles.h:404
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO_ONE
Definition: grid.cc:253
A strust containing the informations needed to search the neighboring cell.
Definition: grid.cc:315
constexpr int p
Proton.
static const std::initializer_list< GridBase::SizeType > ZERO
Definition: grid.cc:250
CellSizeStrategy
Indentifies the strategy of determining the cell size.
Definition: grid.h:33
The Particles class abstracts the storage and manipulation of particles.
Definition: particles.h:33
constexpr int n
Neutron.
Abstracts a list of cells that partition the particles in the experiment into regions of space that c...
Definition: grid.h:79
Make cells as large as possible.
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:245
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:154
ParticleData contains the dynamic information of a certain particle.
Definition: particledata.h:52
Definition: action.h:24