Version: SMASH-1.8
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, double timestep_duration, 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, double timestep_duration, 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...
 
double cell_volume () const
 
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...
 
double cell_volume_
 The volume of a single cell. 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,
double  timestep_duration,
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]timestep_durationDuration of the timestep. It is necessary for formation times treatment: if particle is fully or partially formed before the end of the timestep, it has to be on the grid.
[in]strategyThe strategy for determining the cell size

Definition at line 93 of file grid.h.

96  : Grid{find_min_and_length(particles), std::move(particles),
97  min_cell_length, timestep_duration, strategy} {}

◆ 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,
double  timestep_duration,
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]timestep_durationduration of the timestep in fm/c
[in]strategyThe strategy for determining the cell size
Exceptions
runtime_errorif your box length is smaller than the grid length.

Definition at line 101 of file grid.cc.

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  // This normally equals 1/max_interaction_length, but if the number of cells
139  // is reduced (because of low density) then this value is smaller.
140  std::array<double, 3> index_factor = {1. / max_interaction_length,
141  1. / max_interaction_length,
142  1. / max_interaction_length};
143  for (std::size_t i = 0; i < number_of_cells_.size(); ++i) {
144  number_of_cells_[i] =
145  (strategy == CellSizeStrategy::Largest)
146  ? 2
147  : static_cast<int>(std::floor(length_[i] * index_factor[i])) +
148  // The last cell in each direction can be smaller than
149  // max_interaction_length. In that case periodic boundaries
150  // will not work correctly. Thus, we need to reduce the number
151  // of cells in that direction by one and make the last cell
152  // larger. This basically merges a smaller boundary cell into
153  // a full cell inside the grid. There's a ~0% chance that the
154  // given boundaries create an integral number of cells with
155  // length of max_interaction_length. Therefore, just make the
156  // default number of cells one less than for non-periodic
157  // boundaries.
158  (O == GridOptions::Normal ? 1 : 0);
159  if (number_of_cells_[i] == 0) {
160  throw std::runtime_error(
161  "Input error: Your Box is too small for the grid."
162  "\nThe minimal length of the box is given by:\n" +
163  std::to_string(max_interaction_length) +
164  " fm with your current timestep size dt.\n"
165  "If you have large timesteps please reduce them."
166  "\nPlease take a look at your config.");
167  }
168  // std::nextafter implements a safety margin so that no valid position
169  // inside the grid can reference an out-of-bounds cell
170  if (number_of_cells_[i] > max_cells) {
171  number_of_cells_[i] = max_cells;
172  index_factor[i] = number_of_cells_[i] / length_[i];
173  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
174  index_factor[i] = std::nextafter(index_factor[i], 0.);
175  }
176  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
177  } else if (O == GridOptions::PeriodicBoundaries) {
178  if (number_of_cells_[i] == 1) {
179  number_of_cells_[i] = 2;
180  }
181  index_factor[i] = number_of_cells_[i] / length_[i];
182  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
183  index_factor[i] = std::nextafter(index_factor[i], 0.);
184  }
185  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
186  }
187  }
188 
190  (length_[1] / number_of_cells_[1]) *
191  (length_[2] / number_of_cells_[2]);
192 
193  if (O == GridOptions::Normal &&
194  all_of(number_of_cells_, [](SizeType n) { return n <= 2; })) {
195  // dilute limit:
196  // the grid would have <= 2x2x2 cells, meaning every particle has to be
197  // compared with every other particle anyway. Then we can just as well
198  // fall back to not using the grid at all
199  // For a grid with periodic boundaries the situation is different and we
200  // never want to have a grid smaller than 2x2x2.
201  logg[LGrid].debug(
202  "There would only be ", number_of_cells_,
203  " cells. Therefore the Grid falls back to a single cell / "
204  "particle list.");
205  number_of_cells_ = {1, 1, 1};
206  cell_volume_ = length_[0] * length_[1] * length_[2];
207  cells_.resize(1);
208  cells_.front().reserve(particles.size());
209  std::copy_if(particles.begin(), particles.end(),
210  std::back_inserter(cells_.front()),
211  [&](const ParticleData &p) {
212  return p.xsec_scaling_factor(timestep_duration) > 0.0;
213  }); // filter out the particles that can not interact
214  } else {
215  // construct a normal grid
216  logg[LGrid].debug("min: ", min_position, "\nlength: ", length_,
217  "\ncell_volume: ", cell_volume_,
218  "\ncells: ", number_of_cells_,
219  "\nindex_factor: ", index_factor);
220 
221  // After the grid parameters are determined, we can start placing the
222  // particles in cells.
223  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
224  number_of_cells_[2]);
225 
226  // Returns the one-dimensional cell-index from the position vector inside
227  // the grid.
228  // This simply calculates the distance to min_position and multiplies it
229  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
230  auto &&cell_index_for = [&](const ParticleData &p) {
231  return make_index(
232  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
233  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
234  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
235  };
236 
237  for (const auto &p : particles) {
238  if (p.xsec_scaling_factor(timestep_duration) > 0.0) {
239  const auto idx = cell_index_for(p);
240 #ifndef NDEBUG
241  if (idx >= SizeType(cells_.size())) {
242  logg[LGrid].fatal(
244  "\nan out-of-bounds access would be necessary for the "
245  "particle ",
246  p, "\nfor a grid with the following parameters:\nmin: ",
247  min_position, "\nlength: ", length_,
248  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor,
249  "\ncells_.size: ", cells_.size(), "\nrequested index: ", idx);
250  throw std::runtime_error("out-of-bounds grid access on construction");
251  }
252 #endif
253  cells_[idx].push_back(p);
254  }
255  }
256  }
257 
258  logg[LGrid].debug(cells_);
259 }

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.

◆ cell_volume()

template<GridOptions Options = GridOptions::Normal>
double smash::Grid< Options >::cell_volume ( ) const
inline
Returns
the volume of a single grid cell

Definition at line 142 of file grid.h.

142 { return cell_volume_; }

◆ 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 262 of file grid.cc.

263  {
264  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
265 }
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 155 of file grid.h.

155  {
156  return make_index(idx[0], idx[1], idx[2]);
157  }

◆ 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 275 of file grid.cc.

278  {
279  std::array<SizeType, 3> search_index;
280  SizeType &x = search_index[0];
281  SizeType &y = search_index[1];
282  SizeType &z = search_index[2];
283  SizeType search_cell_index = 0;
284  for (z = 0; z < number_of_cells_[2]; ++z) {
285  for (y = 0; y < number_of_cells_[1]; ++y) {
286  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
287  assert(search_cell_index == make_index(search_index));
288  assert(search_cell_index >= 0);
289  assert(search_cell_index < SizeType(cells_.size()));
290  const ParticleList &search = cells_[search_cell_index];
291  search_cell_callback(search);
292 
293  const auto &dz_list = z == number_of_cells_[2] - 1 ? ZERO : ZERO_ONE;
294  const auto &dy_list = number_of_cells_[1] == 1
295  ? ZERO
296  : y == 0 ? ZERO_ONE
297  : y == number_of_cells_[1] - 1
300  const auto &dx_list = number_of_cells_[0] == 1
301  ? ZERO
302  : x == 0 ? ZERO_ONE
303  : x == number_of_cells_[0] - 1
306  for (SizeType dz : dz_list) {
307  for (SizeType dy : dy_list) {
308  for (SizeType dx : dx_list) {
309  const auto di = make_index(dx, dy, dz);
310  if (di > 0) {
311  neighbor_cell_callback(search, cells_[search_cell_index + di]);
312  }
313  }
314  }
315  }
316  }
317  }
318  }
319 }

◆ 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 341 of file grid.cc.

344  {
345  std::array<SizeType, 3> search_index;
346  SizeType &x = search_index[0];
347  SizeType &y = search_index[1];
348  SizeType &z = search_index[2];
349  SizeType search_cell_index = 0;
350 
351  // defaults:
352  std::array<NeighborLookup, 2> dz_list;
353  std::array<NeighborLookup, 3> dy_list;
354  std::array<NeighborLookup, 3> dx_list;
355 
356  assert(number_of_cells_[2] >= 2);
357  assert(number_of_cells_[1] >= 2);
358  assert(number_of_cells_[0] >= 2);
359 
360  for (z = 0; z < number_of_cells_[2]; ++z) {
361  dz_list[0].index = z;
362  dz_list[1].index = z + 1;
363  if (dz_list[1].index == number_of_cells_[2]) {
364  dz_list[1].index = 0;
365  // last z in the loop, so no need to reset wrap again
366  dz_list[1].wrap = NeedsToWrap::MinusLength;
367  }
368  for (y = 0; y < number_of_cells_[1]; ++y) {
369  dy_list[0].index = y;
370  dy_list[1].index = y - 1;
371  dy_list[2].index = y + 1;
372  dy_list[2].wrap = NeedsToWrap::No;
373  if (y == 0) {
374  dy_list[1] = dy_list[2];
375  dy_list[2].index = number_of_cells_[1] - 1;
376  dy_list[2].wrap = NeedsToWrap::PlusLength;
377  } else if (dy_list[2].index == number_of_cells_[1]) {
378  dy_list[2].index = 0;
379  dy_list[2].wrap = NeedsToWrap::MinusLength;
380  }
381  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
382  dx_list[0].index = x;
383  dx_list[1].index = x - 1;
384  dx_list[2].index = x + 1;
385  dx_list[2].wrap = NeedsToWrap::No;
386  if (x == 0) {
387  dx_list[1] = dx_list[2];
388  dx_list[2].index = number_of_cells_[0] - 1;
389  dx_list[2].wrap = NeedsToWrap::PlusLength;
390  } else if (dx_list[2].index == number_of_cells_[0]) {
391  dx_list[2].index = 0;
392  dx_list[2].wrap = NeedsToWrap::MinusLength;
393  }
394 
395  assert(search_cell_index == make_index(search_index));
396  assert(search_cell_index >= 0);
397  assert(search_cell_index < SizeType(cells_.size()));
398  ParticleList search = cells_[search_cell_index];
399  search_cell_callback(search);
400 
401  auto virtual_search_index = search_index;
402  ThreeVector wrap_vector = {}; // no change
403  auto current_wrap_vector = wrap_vector;
404 
405  for (const auto &dz : dz_list) {
406  if (dz.wrap == NeedsToWrap::MinusLength) {
407  // last dz in the loop, so no need to undo the wrap
408  wrap_vector[2] = -length_[2];
409  virtual_search_index[2] = -1;
410  }
411  for (const auto &dy : dy_list) {
412  // only the last dy in dy_list can wrap
413  if (dy.wrap == NeedsToWrap::MinusLength) {
414  wrap_vector[1] = -length_[1];
415  virtual_search_index[1] = -1;
416  } else if (dy.wrap == NeedsToWrap::PlusLength) {
417  wrap_vector[1] = length_[1];
418  virtual_search_index[1] = number_of_cells_[1];
419  }
420  for (const auto &dx : dx_list) {
421  // only the last dx in dx_list can wrap
422  if (dx.wrap == NeedsToWrap::MinusLength) {
423  wrap_vector[0] = -length_[0];
424  virtual_search_index[0] = -1;
425  } else if (dx.wrap == NeedsToWrap::PlusLength) {
426  wrap_vector[0] = length_[0];
427  virtual_search_index[0] = number_of_cells_[0];
428  }
429  assert(dx.index >= 0);
430  assert(dx.index < number_of_cells_[0]);
431  assert(dy.index >= 0);
432  assert(dy.index < number_of_cells_[1]);
433  assert(dz.index >= 0);
434  assert(dz.index < number_of_cells_[2]);
435  const auto neighbor_cell_index =
436  make_index(dx.index, dy.index, dz.index);
437  assert(neighbor_cell_index >= 0);
438  assert(neighbor_cell_index < SizeType(cells_.size()));
439  if (neighbor_cell_index <= make_index(virtual_search_index)) {
440  continue;
441  }
442 
443  if (wrap_vector != current_wrap_vector) {
444  logg[LGrid].debug("translating search cell by ",
445  wrap_vector - current_wrap_vector);
446  for_each(search, [&](ParticleData &p) {
447  p = p.translated(wrap_vector - current_wrap_vector);
448  });
449  current_wrap_vector = wrap_vector;
450  }
451  neighbor_cell_callback(search, cells_[neighbor_cell_index]);
452  }
453  virtual_search_index[0] = search_index[0];
454  wrap_vector[0] = 0;
455  }
456  virtual_search_index[1] = search_index[1];
457  wrap_vector[1] = 0;
458  }
459  }
460  }
461  }
462 }

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 160 of file grid.h.

◆ cell_volume_

template<GridOptions Options = GridOptions::Normal>
double smash::Grid< Options >::cell_volume_
private

The volume of a single cell.

Definition at line 163 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 166 of file grid.h.

◆ cells_

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

The cell storage.

Definition at line 169 of file grid.h.


The documentation for this class was generated from the following files:
smash::MINUS_ONE_ZERO_ONE
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO_ONE
Definition: grid.cc:270
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::MINUS_ONE_ZERO
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO
Definition: grid.cc:269
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::GridOptions::Normal
Without ghost cells.
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:240
smash::Grid::number_of_cells_
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:166
smash::LGrid
static constexpr int LGrid
Definition: grid.cc:66
smash::ZERO
static const std::initializer_list< GridBase::SizeType > ZERO
Definition: grid.cc:267
smash::NeedsToWrap::PlusLength
smash::Grid::cells_
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:169
smash::CellSizeStrategy::Largest
Make cells as large as possible.
smash::Grid::length_
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:160
smash::GridBase::SizeType
int SizeType
A type to store the sizes.
Definition: grid.h:53
smash::Grid::cell_volume_
double cell_volume_
The volume of a single cell.
Definition: grid.h:163
smash::ZERO_ONE
static const std::initializer_list< GridBase::SizeType > ZERO_ONE
Definition: grid.cc:268
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::GridOptions::PeriodicBoundaries
With ghost cells for periodic boundaries.
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::No
smash::Grid::make_index
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:262
smash::NeedsToWrap::MinusLength