Version: SMASH-1.5
smash::Grid< Options > Class Template Reference

#include <grid.h>

template<GridOptions Options = GridOptions::Normal>
class smash::Grid< Options >

Abstracts a list of cells that partition the particles in the experiment into regions of space that can interact / cannot interact.

This class is used to construct a helper data structure to reduce the combinatorics of finding particle pairs that could interact (scatter). It takes a list of ParticleData objects and sorts them in such a way that it is easy to look only at lists of particles that have a chance of interacting.

Template Parameters
OptionsThis policy parameter determines whether ghost cells are created to support periodic boundaries, or not.

Definition at line 79 of file grid.h.

Inheritance diagram for smash::Grid< Options >:
[legend]
Collaboration diagram for smash::Grid< Options >:
[legend]

Public Member Functions

 Grid (const Particles &particles, double min_cell_length, CellSizeStrategy strategy=CellSizeStrategy::Optimal)
 Constructs a grid from the given particle list particles. More...
 
 Grid (const std::pair< std::array< double, 3 >, std::array< double, 3 >> &min_and_length, const Particles &particles, double min_cell_length, CellSizeStrategy strategy=CellSizeStrategy::Optimal)
 Constructs a grid with the given minimum grid coordinates and grid length. More...
 
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 neighbor cells. More...
 
template<>
void iterate_cells (const std::function< void(const ParticleList &)> &search_cell_callback, const std::function< void(const ParticleList &, const ParticleList &)> &neighbor_cell_callback) const
 Specialization of iterate_cells. More...
 
template<>
void iterate_cells (const std::function< void(const ParticleList &)> &search_cell_callback, const std::function< void(const ParticleList &, const ParticleList &)> &neighbor_cell_callback) const
 Specialization of iterate_cells. More...
 

Private Member Functions

SizeType make_index (SizeType x, SizeType y, SizeType z) const
 
SizeType make_index (std::array< SizeType, 3 > idx) const
 

Private Attributes

const std::array< double, 3 > length_
 The 3 lengths of the complete grid. Used for periodic boundary wrapping. More...
 
std::array< int, 3 > number_of_cells_
 The number of cells in x, y, and z direction. More...
 
std::vector< ParticleList > cells_
 The cell storage. More...
 

Additional Inherited Members

- Public Types inherited from smash::GridBase
typedef int SizeType
 A type to store the sizes. More...
 
- Static Protected Member Functions inherited from smash::GridBase
static std::pair< std::array< double, 3 >, std::array< double, 3 > > find_min_and_length (const Particles &particles)
 

Constructor & Destructor Documentation

◆ Grid() [1/2]

template<GridOptions Options = GridOptions::Normal>
smash::Grid< Options >::Grid ( const Particles particles,
double  min_cell_length,
CellSizeStrategy  strategy = CellSizeStrategy::Optimal 
)
inline

Constructs a grid from the given particle list particles.

It automatically determines the necessary size for the grid from the positions of the particles.

Parameters
[in]particlesThe particles to place onto the grid.
[in]min_cell_lengthThe minimal length a cell must have.
[in]strategyThe strategy for determining the cell size

Definition at line 90 of file grid.h.

92  : Grid{find_min_and_length(particles), std::move(particles),
93  min_cell_length, strategy} {}
static std::pair< std::array< double, 3 >, std::array< double, 3 > > find_min_and_length(const Particles &particles)
Definition: grid.cc:71
Grid(const Particles &particles, double min_cell_length, CellSizeStrategy strategy=CellSizeStrategy::Optimal)
Constructs a grid from the given particle list particles.
Definition: grid.h:90

◆ Grid() [2/2]

template<GridOptions O>
template smash::Grid< Options >::Grid ( const std::pair< std::array< double, 3 >, std::array< double, 3 >> &  min_and_length,
const Particles particles,
double  min_cell_length,
CellSizeStrategy  strategy = CellSizeStrategy::Optimal 
)

Constructs a grid with the given minimum grid coordinates and grid length.

If you need periodic boundaries you have to use this constructor to set the correct length to use for wrapping particles around the borders.

Parameters
[in]min_and_lengthA pair consisting of the three min coordinates and the three lengths.
[in]particlesThe particles to place onto the grid.
[in]min_cell_lengthThe minimal length a cell must have.
[in]strategyThe strategy for determining the cell size

Definition at line 100 of file grid.cc.

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()), [](const ParticleData &p) {
196  return p.xsec_scaling_factor() > 0.0;
197  }); // filter out the particles that can not interact
198  } else {
199  // construct a normal grid
200  log.debug("min: ", min_position, "\nlength: ", length_,
201  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor);
202 
203  // After the grid parameters are determined, we can start placing the
204  // particles in cells.
205  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
206  number_of_cells_[2]);
207 
208  // Returns the one-dimensional cell-index from the position vector inside
209  // the grid.
210  // This simply calculates the distance to min_position and multiplies it
211  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
212  auto &&cell_index_for = [&](const ParticleData &p) {
213  return make_index(
214  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
215  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
216  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
217  };
218 
219  for (const auto &p : particles) {
220  if (p.xsec_scaling_factor() > 0.0) {
221  const auto idx = cell_index_for(p);
222 #ifndef NDEBUG
223  if (idx >= SizeType(cells_.size())) {
224  log.fatal(
226  "\nan out-of-bounds access would be necessary for the "
227  "particle ",
228  p, "\nfor a grid with the following parameters:\nmin: ",
229  min_position, "\nlength: ", length_,
230  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor,
231  "\ncells_.size: ", cells_.size(), "\nrequested index: ", idx);
232  throw std::runtime_error("out-of-bounds grid access on construction");
233  }
234 #endif
235  cells_[idx].push_back(p);
236  }
237  }
238  }
239 
240  log.debug(cells_);
241 }
With ghost cells for periodic boundaries.
Without ghost cells.
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:154
#define source_location
Hackery that is required to output the location in the source code where the log statement occurs...
Definition: logging.h:246
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:151
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
constexpr int p
Proton.
constexpr int n
Neutron.
Make cells as large as possible.
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:244
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:148

Member Function Documentation

◆ iterate_cells() [1/3]

template<GridOptions Options = GridOptions::Normal>
void smash::Grid< Options >::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 neighbor cells.

The neighbor cells are constructed like this:

  • one cell at x+1
  • three cells (x-1,x,x+1) at y+1
  • nine cells (x-1, y-1)...(x+1, y+1) at z+1
Parameters
[in]search_cell_callbackA callable called for/with every non-empty cell in the grid.
[in]neighbor_cell_callbackA callable called for/with every non-empty cell and adjacent cell combination. For a periodic grid, the first argument will be adjusted to wrap around the grid.

◆ make_index() [1/2]

template<GridOptions Options>
Grid< Options >::SizeType smash::Grid< Options >::make_index ( SizeType  x,
SizeType  y,
SizeType  z 
) const
inlineprivate
Returns
the one-dimensional cell-index from the 3-dim index x, y, z.

Definition at line 244 of file grid.cc.

245  {
246  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
247 }
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:151
Here is the caller graph for this function:

◆ make_index() [2/2]

template<GridOptions Options = GridOptions::Normal>
SizeType smash::Grid< Options >::make_index ( std::array< SizeType, 3 >  idx) const
inlineprivate
Returns
the one-dimensional cell-index from the 3-dim index idx. This is a convenience overload for the above function.

Definition at line 143 of file grid.h.

143  {
144  return make_index(idx[0], idx[1], idx[2]);
145  }
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:244

◆ iterate_cells() [2/3]

template<>
void smash::Grid< GridOptions::Normal >::iterate_cells ( const std::function< void(const ParticleList &)> &  search_cell_callback,
const std::function< void(const ParticleList &, const ParticleList &)> &  neighbor_cell_callback 
) const

Specialization of iterate_cells.

Definition at line 251 of file grid.cc.

254  {
255  std::array<SizeType, 3> search_index;
256  SizeType &x = search_index[0];
257  SizeType &y = search_index[1];
258  SizeType &z = search_index[2];
259  SizeType search_cell_index = 0;
260  for (z = 0; z < number_of_cells_[2]; ++z) {
261  for (y = 0; y < number_of_cells_[1]; ++y) {
262  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
263  assert(search_cell_index == make_index(search_index));
264  assert(search_cell_index >= 0);
265  assert(search_cell_index < SizeType(cells_.size()));
266  const ParticleList &search = cells_[search_cell_index];
267  search_cell_callback(search);
268 
269  const auto dz_list = z == number_of_cells_[2] - 1
270  ? std::initializer_list<SizeType>{0}
271  : std::initializer_list<SizeType>{0, 1};
272  const auto dy_list =
273  number_of_cells_[1] == 1
274  ? std::initializer_list<SizeType>{0}
275  : y == 0 ? std::initializer_list<SizeType>{0, 1}
276  : y == number_of_cells_[1] - 1
277  ? std::initializer_list<SizeType>{-1, 0}
278  : std::initializer_list<SizeType>{-1, 0, 1};
279  const auto dx_list =
280  number_of_cells_[0] == 1
281  ? std::initializer_list<SizeType>{0}
282  : x == 0 ? std::initializer_list<SizeType>{0, 1}
283  : x == number_of_cells_[0] - 1
284  ? std::initializer_list<SizeType>{-1, 0}
285  : std::initializer_list<SizeType>{-1, 0, 1};
286  for (SizeType dz : dz_list) {
287  for (SizeType dy : dy_list) {
288  for (SizeType dx : dx_list) {
289  const auto di = make_index(dx, dy, dz);
290  if (di > 0) {
291  neighbor_cell_callback(search, cells_[search_cell_index + di]);
292  }
293  }
294  }
295  }
296  }
297  }
298  }
299 }
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:154
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:151
int SizeType
A type to store the sizes.
Definition: grid.h:53
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:244

◆ iterate_cells() [3/3]

template<>
void smash::Grid< GridOptions::PeriodicBoundaries >::iterate_cells ( const std::function< void(const ParticleList &)> &  search_cell_callback,
const std::function< void(const ParticleList &, const ParticleList &)> &  neighbor_cell_callback 
) const

Specialization of iterate_cells.

Definition at line 321 of file grid.cc.

324  {
325  const auto &log = logger<LogArea::Grid>();
326 
327  std::array<SizeType, 3> search_index;
328  SizeType &x = search_index[0];
329  SizeType &y = search_index[1];
330  SizeType &z = search_index[2];
331  SizeType search_cell_index = 0;
332 
333  // defaults:
334  std::array<NeighborLookup, 2> dz_list;
335  std::array<NeighborLookup, 3> dy_list;
336  std::array<NeighborLookup, 3> dx_list;
337 
338  assert(number_of_cells_[2] >= 2);
339  assert(number_of_cells_[1] >= 2);
340  assert(number_of_cells_[0] >= 2);
341 
342  for (z = 0; z < number_of_cells_[2]; ++z) {
343  dz_list[0].index = z;
344  dz_list[1].index = z + 1;
345  if (dz_list[1].index == number_of_cells_[2]) {
346  dz_list[1].index = 0;
347  // last z in the loop, so no need to reset wrap again
348  dz_list[1].wrap = NeedsToWrap::MinusLength;
349  }
350  for (y = 0; y < number_of_cells_[1]; ++y) {
351  dy_list[0].index = y;
352  dy_list[1].index = y - 1;
353  dy_list[2].index = y + 1;
354  dy_list[2].wrap = NeedsToWrap::No;
355  if (y == 0) {
356  dy_list[1] = dy_list[2];
357  dy_list[2].index = number_of_cells_[1] - 1;
358  dy_list[2].wrap = NeedsToWrap::PlusLength;
359  } else if (dy_list[2].index == number_of_cells_[1]) {
360  dy_list[2].index = 0;
361  dy_list[2].wrap = NeedsToWrap::MinusLength;
362  }
363  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
364  dx_list[0].index = x;
365  dx_list[1].index = x - 1;
366  dx_list[2].index = x + 1;
367  dx_list[2].wrap = NeedsToWrap::No;
368  if (x == 0) {
369  dx_list[1] = dx_list[2];
370  dx_list[2].index = number_of_cells_[0] - 1;
371  dx_list[2].wrap = NeedsToWrap::PlusLength;
372  } else if (dx_list[2].index == number_of_cells_[0]) {
373  dx_list[2].index = 0;
374  dx_list[2].wrap = NeedsToWrap::MinusLength;
375  }
376 
377  assert(search_cell_index == make_index(search_index));
378  assert(search_cell_index >= 0);
379  assert(search_cell_index < SizeType(cells_.size()));
380  ParticleList search = cells_[search_cell_index];
381  search_cell_callback(search);
382 
383  auto virtual_search_index = search_index;
384  ThreeVector wrap_vector = {}; // no change
385  auto current_wrap_vector = wrap_vector;
386 
387  for (const auto &dz : dz_list) {
388  if (dz.wrap == NeedsToWrap::MinusLength) {
389  // last dz in the loop, so no need to undo the wrap
390  wrap_vector[2] = -length_[2];
391  virtual_search_index[2] = -1;
392  }
393  for (const auto &dy : dy_list) {
394  // only the last dy in dy_list can wrap
395  if (dy.wrap == NeedsToWrap::MinusLength) {
396  wrap_vector[1] = -length_[1];
397  virtual_search_index[1] = -1;
398  } else if (dy.wrap == NeedsToWrap::PlusLength) {
399  wrap_vector[1] = length_[1];
400  virtual_search_index[1] = number_of_cells_[1];
401  }
402  for (const auto &dx : dx_list) {
403  // only the last dx in dx_list can wrap
404  if (dx.wrap == NeedsToWrap::MinusLength) {
405  wrap_vector[0] = -length_[0];
406  virtual_search_index[0] = -1;
407  } else if (dx.wrap == NeedsToWrap::PlusLength) {
408  wrap_vector[0] = length_[0];
409  virtual_search_index[0] = number_of_cells_[0];
410  }
411  assert(dx.index >= 0);
412  assert(dx.index < number_of_cells_[0]);
413  assert(dy.index >= 0);
414  assert(dy.index < number_of_cells_[1]);
415  assert(dz.index >= 0);
416  assert(dz.index < number_of_cells_[2]);
417  const auto neighbor_cell_index =
418  make_index(dx.index, dy.index, dz.index);
419  assert(neighbor_cell_index >= 0);
420  assert(neighbor_cell_index < SizeType(cells_.size()));
421  if (neighbor_cell_index <= make_index(virtual_search_index)) {
422  continue;
423  }
424 
425  if (wrap_vector != current_wrap_vector) {
426  log.debug("translating search cell by ",
427  wrap_vector - current_wrap_vector);
428  for_each(search, [&](ParticleData &p) {
429  p = p.translated(wrap_vector - current_wrap_vector);
430  });
431  current_wrap_vector = wrap_vector;
432  }
433  neighbor_cell_callback(search, cells_[neighbor_cell_index]);
434  }
435  virtual_search_index[0] = search_index[0];
436  wrap_vector[0] = 0;
437  }
438  virtual_search_index[1] = search_index[1];
439  wrap_vector[1] = 0;
440  }
441  }
442  }
443  }
444 }
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:154
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:151
int SizeType
A type to store the sizes.
Definition: grid.h:53
UnaryFunction for_each(Container &&c, UnaryFunction &&f)
Convenience wrapper for std::for_each that operates on a complete container.
Definition: algorithms.h:96
constexpr int p
Proton.
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:244
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:148

Member Data Documentation

◆ length_

template<GridOptions Options = GridOptions::Normal>
const std::array<double, 3> smash::Grid< Options >::length_
private

The 3 lengths of the complete grid. Used for periodic boundary wrapping.

Definition at line 148 of file grid.h.

◆ number_of_cells_

template<GridOptions Options = GridOptions::Normal>
std::array<int, 3> smash::Grid< Options >::number_of_cells_
private

The number of cells in x, y, and z direction.

Definition at line 151 of file grid.h.

◆ cells_

template<GridOptions Options = GridOptions::Normal>
std::vector<ParticleList> smash::Grid< Options >::cells_
private

The cell storage.

Definition at line 154 of file grid.h.


The documentation for this class was generated from the following files: