Version: SMASH-1.7
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

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(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
static std::pair< std::array< double, 3 >, std::array< double, 3 > > find_min_and_length(const Particles &particles)
Definition: grid.cc:71

Here is the caller graph for this function:

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 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  cell_volume_ = length_[0] * length_[1] * length_[2];
112  cells_.clear();
113  cells_.reserve(1);
114  cells_.emplace_back(particles.copy_to_vector());
115  return;
116  }
117 
118  // The number of cells is determined by the min and max coordinates where
119  // particles are positioned and the maximal interaction length (which equals
120  // the length of a cell).
121  // But don't let the number of cells exceed the actual number of particles.
122  // That would be overkill. Let max_cells³ ≤ particle_count (conversion to
123  // int truncates).
124  // Consider that particle placement into cells uses half-open intervals. Thus
125  // a cell includes particles in [0, a[. The next cell [a, 2a[. And so on. This
126  // is important for calculating the number of cells. If length * index_factor
127  // (equivalent to length / max_interaction_length) is integral, then
128  // length * index_factor + 1 determines the number of required cells. That's
129  // because the last cell will then store particles in the interval
130  // [length, length + max_interaction_length[. The code below achieves this
131  // effect by rounding down (floor) and adding 1 afterwards.
132  const int max_cells =
133  (O == GridOptions::Normal)
134  ? std::cbrt(particle_count)
135  : std::max(2, static_cast<int>(std::cbrt(particle_count)));
136 
137  // This normally equals 1/max_interaction_length, but if the number of cells
138  // is reduced (because of low density) then this value is smaller.
139  std::array<double, 3> index_factor = {1. / max_interaction_length,
140  1. / max_interaction_length,
141  1. / max_interaction_length};
142  for (std::size_t i = 0; i < number_of_cells_.size(); ++i) {
143  number_of_cells_[i] =
144  (strategy == CellSizeStrategy::Largest)
145  ? 2
146  : static_cast<int>(std::floor(length_[i] * index_factor[i])) +
147  // The last cell in each direction can be smaller than
148  // max_interaction_length. In that case periodic boundaries
149  // will not work correctly. Thus, we need to reduce the number
150  // of cells in that direction by one and make the last cell
151  // larger. This basically merges a smaller boundary cell into
152  // a full cell inside the grid. There's a ~0% chance that the
153  // given boundaries create an integral number of cells with
154  // length of max_interaction_length. Therefore, just make the
155  // default number of cells one less than for non-periodic
156  // boundaries.
157  (O == GridOptions::Normal ? 1 : 0);
158  if (number_of_cells_[i] == 0) {
159  throw std::runtime_error(
160  "Input error: Your Box is too small for the grid."
161  "\nThe minimal length of the box is given by:\n" +
162  std::to_string(max_interaction_length) +
163  " fm with your current timestep size dt.\n"
164  "If you have large timesteps please reduce them."
165  "\nPlease take a look at your config.");
166  }
167  // std::nextafter implements a safety margin so that no valid position
168  // inside the grid can reference an out-of-bounds cell
169  if (number_of_cells_[i] > max_cells) {
170  number_of_cells_[i] = max_cells;
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  } else if (O == GridOptions::PeriodicBoundaries) {
177  if (number_of_cells_[i] == 1) {
178  number_of_cells_[i] = 2;
179  }
180  index_factor[i] = number_of_cells_[i] / length_[i];
181  while (index_factor[i] * length_[i] >= number_of_cells_[i]) {
182  index_factor[i] = std::nextafter(index_factor[i], 0.);
183  }
184  assert(index_factor[i] * length_[i] < number_of_cells_[i]);
185  }
186  }
187 
188  cell_volume_ = (length_[0] / number_of_cells_[0]) *
189  (length_[1] / number_of_cells_[1]) *
190  (length_[2] / number_of_cells_[2]);
191 
192  const auto &log = logger<LogArea::Grid>();
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  log.debug("There would only be ", number_of_cells_,
202  " cells. Therefore the Grid falls back to a single cell / "
203  "particle list.");
204  number_of_cells_ = {1, 1, 1};
205  cell_volume_ = length_[0] * length_[1] * length_[2];
206  cells_.resize(1);
207  cells_.front().reserve(particles.size());
208  std::copy_if(particles.begin(), particles.end(),
209  std::back_inserter(cells_.front()),
210  [&](const ParticleData &p) {
211  return p.xsec_scaling_factor(timestep_duration) > 0.0;
212  }); // filter out the particles that can not interact
213  } else {
214  // construct a normal grid
215  log.debug("min: ", min_position, "\nlength: ", length_,
216  "\ncell_volume: ", cell_volume_, "\ncells: ", number_of_cells_,
217  "\nindex_factor: ", index_factor);
218 
219  // After the grid parameters are determined, we can start placing the
220  // particles in cells.
221  cells_.resize(number_of_cells_[0] * number_of_cells_[1] *
222  number_of_cells_[2]);
223 
224  // Returns the one-dimensional cell-index from the position vector inside
225  // the grid.
226  // This simply calculates the distance to min_position and multiplies it
227  // with index_factor to determine the 3 x,y,z indexes to pass to make_index.
228  auto &&cell_index_for = [&](const ParticleData &p) {
229  return make_index(
230  std::floor((p.position()[1] - min_position[0]) * index_factor[0]),
231  std::floor((p.position()[2] - min_position[1]) * index_factor[1]),
232  std::floor((p.position()[3] - min_position[2]) * index_factor[2]));
233  };
234 
235  for (const auto &p : particles) {
236  if (p.xsec_scaling_factor(timestep_duration) > 0.0) {
237  const auto idx = cell_index_for(p);
238 #ifndef NDEBUG
239  if (idx >= SizeType(cells_.size())) {
240  log.fatal(
242  "\nan out-of-bounds access would be necessary for the "
243  "particle ",
244  p, "\nfor a grid with the following parameters:\nmin: ",
245  min_position, "\nlength: ", length_,
246  "\ncells: ", number_of_cells_, "\nindex_factor: ", index_factor,
247  "\ncells_.size: ", cells_.size(), "\nrequested index: ", idx);
248  throw std::runtime_error("out-of-bounds grid access on construction");
249  }
250 #endif
251  cells_[idx].push_back(p);
252  }
253  }
254  }
255 
256  log.debug(cells_);
257 }
With ghost cells for periodic boundaries.
Without ghost cells.
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:169
#define source_location
Hackery that is required to output the location in the source code where the log statement occurs...
Definition: logging.h:253
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:166
int SizeType
A type to store the sizes.
Definition: grid.h:53
double cell_volume_
The volume of a single cell.
Definition: grid.h:163
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:260
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:160

Here is the call graph for this function:

Member Function Documentation

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.
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_; }
double cell_volume_
The volume of a single cell.
Definition: grid.h:163
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 260 of file grid.cc.

261  {
262  return (z * number_of_cells_[1] + y) * number_of_cells_[0] + x;
263 }
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:166

Here is the caller graph for this function:

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  }
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:260
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 273 of file grid.cc.

276  {
277  std::array<SizeType, 3> search_index;
278  SizeType &x = search_index[0];
279  SizeType &y = search_index[1];
280  SizeType &z = search_index[2];
281  SizeType search_cell_index = 0;
282  for (z = 0; z < number_of_cells_[2]; ++z) {
283  for (y = 0; y < number_of_cells_[1]; ++y) {
284  for (x = 0; x < number_of_cells_[0]; ++x, ++search_cell_index) {
285  assert(search_cell_index == make_index(search_index));
286  assert(search_cell_index >= 0);
287  assert(search_cell_index < SizeType(cells_.size()));
288  const ParticleList &search = cells_[search_cell_index];
289  search_cell_callback(search);
290 
291  const auto &dz_list = z == number_of_cells_[2] - 1 ? ZERO : ZERO_ONE;
292  const auto &dy_list = number_of_cells_[1] == 1
293  ? ZERO
294  : y == 0 ? ZERO_ONE
295  : y == number_of_cells_[1] - 1
298  const auto &dx_list = number_of_cells_[0] == 1
299  ? ZERO
300  : x == 0 ? ZERO_ONE
301  : x == number_of_cells_[0] - 1
304  for (SizeType dz : dz_list) {
305  for (SizeType dy : dy_list) {
306  for (SizeType dx : dx_list) {
307  const auto di = make_index(dx, dy, dz);
308  if (di > 0) {
309  neighbor_cell_callback(search, cells_[search_cell_index + di]);
310  }
311  }
312  }
313  }
314  }
315  }
316  }
317 }
static const std::initializer_list< GridBase::SizeType > ZERO_ONE
Definition: grid.cc:266
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:169
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:166
int SizeType
A type to store the sizes.
Definition: grid.h:53
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO
Definition: grid.cc:267
static const std::initializer_list< GridBase::SizeType > MINUS_ONE_ZERO_ONE
Definition: grid.cc:268
static const std::initializer_list< GridBase::SizeType > ZERO
Definition: grid.cc:265
SizeType make_index(SizeType x, SizeType y, SizeType z) const
Definition: grid.cc:260

Here is the call graph for this function:

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

342  {
343  const auto &log = logger<LogArea::Grid>();
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  log.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 }
std::vector< ParticleList > cells_
The cell storage.
Definition: grid.h:169
std::array< int, 3 > number_of_cells_
The number of cells in x, y, and z direction.
Definition: grid.h:166
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:260
const std::array< double, 3 > length_
The 3 lengths of the complete grid. Used for periodic boundary wrapping.
Definition: grid.h:160

Here is the call graph for this function:

Member Data Documentation

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.

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.

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.

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: