Molecular Dynamics Simulation  1.0
LinkedCellsContainer.cpp
Go to the documentation of this file.
1 #include "LinkedCellsContainer.h"
2 
3 #include <algorithm>
4 #include <cmath>
5 #include <random>
6 
7 #include "cells/Cell.h"
8 #include "io/logger/Logger.h"
13 #include "utils/ArrayUtils.h"
14 
15 /*
16  Methods of the LinkedCellsContainer
17 */
18 LinkedCellsContainer::LinkedCellsContainer(const std::array<double, 3>& _domain_size, double _cutoff_radius,
19  const std::array<BoundaryCondition, 6>& _boundary_types, int _n)
20  : domain_size(_domain_size), cutoff_radius(_cutoff_radius), boundary_types(_boundary_types) {
21  domain_num_cells = {std::max(static_cast<int>(std::floor(_domain_size[0] / cutoff_radius)), 1),
22  std::max(static_cast<int>(std::floor(_domain_size[1] / cutoff_radius)), 1),
23  std::max(static_cast<int>(std::floor(_domain_size[2] / cutoff_radius)), 1)};
24 
25  cell_size = {_domain_size[0] / domain_num_cells[0], _domain_size[1] / domain_num_cells[1], _domain_size[2] / domain_num_cells[2]};
26 
27  // reserve the memory for the cells
28  cells.reserve((domain_num_cells[0] + 2) * (domain_num_cells[1] + 2) * (domain_num_cells[2] + 2));
29 
30  // create the cells with the correct cell-type and add them to the cells vector and the corresponding cell reference vector
31  initCells();
32 
33  // add the neighbour references to the cells
35 
36  // create the iteration orders
38 
39  // reserve the memory for the particles to prevent reallocation during insertion
40  particles.reserve(_n);
41 
42  Logger::logger->debug("Created LinkedCellsContainer with boundaries [{}, {}, {}] and cutoff radius {}", domain_size[0], domain_size[1],
44  Logger::logger->debug("Created LinkedCellsContainer with {} domain cells (of which {} are at the boundary) and {} halo cells",
46  Logger::logger->debug("Cells per dimension: [{}, {}, {}]", domain_num_cells[0], domain_num_cells[1], domain_num_cells[2]);
47  Logger::logger->debug("Calculated cell size: [{}, {}, {}]", cell_size[0], cell_size[1], cell_size[2]);
48 }
49 
51  Cell* cell = particlePosToCell(p.getX());
52 
53  if (cell == nullptr) {
54  Logger::logger->error("Particle to insert is out of bounds, position: [{}, {}, {}]", p.getX()[0], p.getX()[1], p.getX()[2]);
55  throw std::runtime_error("Attempted to insert particle out of bounds");
56  }
57  // if (cell->getCellType() == Cell::CellType::HALO) {
58  // Logger::logger->warn("Particle to insert is in halo cell. Position: [{}, {}, {}]", p.getX()[0], p.getX()[1], p.getX()[2]);
59  // throw std::runtime_error("Attempted to insert particle into halo cell");
60  // }
61 
62  size_t old_capacity = particles.capacity();
63  particles.push_back(p);
64 
65  if (old_capacity != particles.capacity()) {
67  } else {
68  cell->addParticleReference(&particles.back());
69  }
70 }
71 
73  Cell* cell = particlePosToCell(p.getX());
74 
75  if (cell == nullptr) {
76  Logger::logger->error("Particle to insert is outside of cells. Position: [{}, {}, {}]", p.getX()[0], p.getX()[1], p.getX()[2]);
77  throw std::runtime_error("Attempted to insert particle out of bounds");
78  }
79  // if (cell->getCellType() == Cell::CellType::HALO) {
80  // Logger::logger->warn("Particle to insert is in halo cell. Position: [{}, {}, {}]", p.getX()[0], p.getX()[1], p.getX()[2]);
81  // throw std::runtime_error("Attempted to insert particle into halo cell");
82  // }
83 
84  size_t old_capacity = particles.capacity();
85  particles.push_back(std::move(p));
86 
87  if (old_capacity != particles.capacity()) {
89  } else {
90  cell->addParticleReference(&particles.back());
91  }
92 }
93 
95  // update the particle references in the cells in case the particles have moved
97 
98  // delete particles from halo cells
100 
101  // teleport particles to other side of container
103 
104  // update the particle references in the cells in case the particles have moved
106 }
107 
108 void LinkedCellsContainer::applySimpleForces(const std::vector<std::shared_ptr<SimpleForceSource>>& simple_force_sources) {
109  for (Particle& p : particles) {
110  if (p.isLocked()) continue;
111  for (const auto& force_source : simple_force_sources) {
112  p.setF(p.getF() + force_source->calculateForce(p));
113  }
114  }
115 }
116 
117 void LinkedCellsContainer::applyPairwiseForces(const std::vector<std::shared_ptr<PairwiseForceSource>>& force_sources) {
118  // apply the boundary conditions
120  size_t original_particle_length = PeriodicBoundaryType::applyBoundaryConditions(*this);
121 
122  for (auto& it_order : iteration_orders) {
123 #ifdef _OPENMP
124 #pragma omp parallel for schedule(dynamic)
125 #endif
126  for (Cell* cell : it_order) {
127  if (cell->getParticleReferences().empty()) continue;
128 
129  // Calculate cell internal forces
130  for (auto it1 = cell->getParticleReferences().begin(); it1 != cell->getParticleReferences().end(); ++it1) {
131  Particle* p = *it1;
132  // calculate the forces between the particle and the particles in the same cell
133  // uses direct sum with newtons third law
134  for (auto it2 = (it1 + 1); it2 != cell->getParticleReferences().end(); ++it2) {
135  if (p->isLocked() && (*it2)->isLocked()) continue;
136  Particle* q = *it2;
137  std::array<double, 3> total_force{0, 0, 0};
138  for (auto& force : force_sources) {
139  total_force = total_force + force->calculateForce(*p, *q);
140  }
141  p->addF(total_force);
142  q->subF(total_force);
143  }
144  }
145 
146  // Calculate cell boundary forces
147  for (Cell* neighbour_cell : cell->getNeighbourReferences()) {
148  if (cell < neighbour_cell) continue;
149  if (neighbour_cell->getParticleReferences().empty()) continue;
150 
151  for (Particle* p : cell->getParticleReferences()) {
152  for (Particle* neighbour_particle : neighbour_cell->getParticleReferences()) {
153  if (p->isLocked() && neighbour_particle->isLocked()) continue;
154  if (ArrayUtils::L2NormSquared(p->getX() - neighbour_particle->getX()) > cutoff_radius * cutoff_radius) continue;
155 
156  auto total_force = std::array<double, 3>{0, 0, 0};
157  for (const auto& force_source : force_sources) {
158  total_force = total_force + force_source->calculateForce(*p, *neighbour_particle);
159  }
160  p->addF(total_force);
161  neighbour_particle->subF(total_force);
162  }
163  }
164  }
165  }
166  }
167 
168  // remove the periodic halo particles
169  particles.erase(particles.begin() + original_particle_length, particles.end());
171 }
172 
173 void LinkedCellsContainer::applyTargettedForces(const std::vector<std::shared_ptr<TargettedForceSource>>& force_sources,
174  double curr_simulation_time) {
175  for (const auto& force_source : force_sources) {
176  force_source->applyForce(particles, curr_simulation_time);
177  }
178 }
179 
181  Logger::logger->debug("Reserving space for {} particles", n);
182 
183  size_t old_capacity = particles.capacity();
184  particles.reserve(n);
185 
186  if (old_capacity != particles.capacity()) {
188  }
189 }
190 
191 size_t LinkedCellsContainer::size() const { return particles.size(); }
192 
194 
195 std::vector<Particle>::iterator LinkedCellsContainer::begin() { return particles.begin(); }
196 
197 std::vector<Particle>::iterator LinkedCellsContainer::end() { return particles.end(); }
198 
199 std::vector<Particle>::const_iterator LinkedCellsContainer::begin() const { return particles.begin(); }
200 
201 std::vector<Particle>::const_iterator LinkedCellsContainer::end() const { return particles.end(); }
202 
203 const std::vector<Particle>& LinkedCellsContainer::getParticles() const { return particles; }
204 
205 const std::array<double, 3>& LinkedCellsContainer::getDomainSize() const { return domain_size; }
206 
208 
209 const std::vector<Cell>& LinkedCellsContainer::getCells() { return cells; }
210 
211 const std::vector<Cell*>& LinkedCellsContainer::getBoundaryCells() const { return boundary_cell_references; }
212 
213 const std::array<double, 3>& LinkedCellsContainer::getCellSize() const { return cell_size; }
214 
215 const std::array<int, 3>& LinkedCellsContainer::getDomainNumCells() const { return domain_num_cells; }
216 
217 int LinkedCellsContainer::cellCoordToCellIndex(int cx, int cy, int cz) const {
218  if (cx < -1 || cx > domain_num_cells[0] || cy < -1 || cy > domain_num_cells[1] || cz < -1 || cz > domain_num_cells[2]) {
219  return -1;
220  }
221  return (cx + 1) * (domain_num_cells[1] + 2) * (domain_num_cells[2] + 2) + (cy + 1) * (domain_num_cells[2] + 2) + (cz + 1);
222 }
223 
224 Cell* LinkedCellsContainer::particlePosToCell(const std::array<double, 3>& pos) { return particlePosToCell(pos[0], pos[1], pos[2]); }
225 
226 Cell* LinkedCellsContainer::particlePosToCell(double x, double y, double z) {
227  int cx = static_cast<int>(std::floor(x / cell_size[0]));
228  int cy = static_cast<int>(std::floor(y / cell_size[1]));
229  int cz = static_cast<int>(std::floor(z / cell_size[2]));
230 
231  int cell_index = cellCoordToCellIndex(cx, cy, cz);
232  if (cell_index == -1) {
233  Logger::logger->error("Particle is outside of cells. Position: [{}, {}, {}]", x, y, z);
234  throw std::runtime_error("A particle is outside of the cells");
235  }
236 
237  return &cells[cell_index];
238 }
239 
241  switch (bc) {
243  return "Outflow";
245  return "Reflective";
247  return "Periodic";
248  default:
249  return "Unknown";
250  }
251 };
252 
253 /*
254  Private methods of the LinkedCellsContainer
255 */
256 
258  for (int cx = -1; cx < domain_num_cells[0] + 1; ++cx) {
259  for (int cy = -1; cy < domain_num_cells[1] + 1; ++cy) {
260  for (int cz = -1; cz < domain_num_cells[2] + 1; ++cz) {
261  if (cx < 0 || cx >= domain_num_cells[0] || cy < 0 || cy >= domain_num_cells[1] || cz < 0 || cz >= domain_num_cells[2]) {
262  cells.emplace_back(Cell::CellType::HALO);
263  halo_cell_references.push_back(&cells.back());
264 
265  if (cx == -1) {
266  left_halo_cell_references.push_back(&cells.back());
267  }
268  if (cx == domain_num_cells[0]) {
269  right_halo_cell_references.push_back(&cells.back());
270  }
271  if (cy == -1) {
272  bottom_halo_cell_references.push_back(&cells.back());
273  }
274  if (cy == domain_num_cells[1]) {
275  top_halo_cell_references.push_back(&cells.back());
276  }
277  if (cz == -1) {
278  back_halo_cell_references.push_back(&cells.back());
279  }
280  if (cz == domain_num_cells[2]) {
281  front_halo_cell_references.push_back(&cells.back());
282  }
283  } else if (cx == 0 || cx == domain_num_cells[0] - 1 || cy == 0 || cy == domain_num_cells[1] - 1 || cz == 0 ||
284  cz == domain_num_cells[2] - 1) {
285  cells.emplace_back(Cell::CellType::BOUNDARY);
286  boundary_cell_references.push_back(&cells.back());
287  domain_cell_references.push_back(&cells.back());
288 
289  if (cx == 0) {
290  left_boundary_cell_references.push_back(&cells.back());
291  }
292  if (cx == domain_num_cells[0] - 1) {
293  right_boundary_cell_references.push_back(&cells.back());
294  }
295  if (cy == 0) {
296  bottom_boundary_cell_references.push_back(&cells.back());
297  }
298  if (cy == domain_num_cells[1] - 1) {
299  top_boundary_cell_references.push_back(&cells.back());
300  }
301  if (cz == 0) {
302  back_boundary_cell_references.push_back(&cells.back());
303  }
304  if (cz == domain_num_cells[2] - 1) {
305  front_boundary_cell_references.push_back(&cells.back());
306  }
307  } else {
308  cells.emplace_back(Cell::CellType::INNER);
309  domain_cell_references.push_back(&cells.back());
310  }
311  }
312  }
313  }
314 }
315 
317  // Loop through each cell according to their cell coordinates
318  for (int cx = -1; cx < domain_num_cells[0] + 1; ++cx) {
319  for (int cy = -1; cy < domain_num_cells[1] + 1; ++cy) {
320  for (int cz = -1; cz < domain_num_cells[2] + 1; ++cz) {
321  Cell& cell = cells.at(cellCoordToCellIndex(cx, cy, cz));
322 
323  // Loop through each of the current cells neighbour cells according to their cell coordinates
324  // except the current cell itself
325  for (int dx = -1; dx < 2; ++dx) {
326  for (int dy = -1; dy < 2; ++dy) {
327  for (int dz = -1; dz < 2; ++dz) {
328  if (dx == 0 && dy == 0 && dz == 0) continue;
329 
330  // Get the cell index of the current neighbour cell
331  int cell_index = cellCoordToCellIndex(cx + dx, cy + dy, cz + dz);
332 
333  // If the neighbour cell would be out of bounds, skip it
334  if (cell_index == -1) continue;
335 
336  // Add the neighbour to the current cells neighbour references
337  Cell& curr_neighbour = cells.at(cell_index);
338  cell.addNeighbourReference(&curr_neighbour);
339  }
340  }
341  }
342  }
343  }
344  }
345 }
346 
348 #if PARALLEL_V2
349  Logger::logger->warn("Creating iteration orders for parallel v2");
350  std::vector<Cell*> iteration_order;
351 
352  for (size_t num = 0; num < cells.size(); ++num) {
353  iteration_order.push_back(&cells[num]);
354  }
355 
356  // shuffle the iteration order
357  std::random_device rd;
358  std::mt19937 g(rd());
359  std::shuffle(iteration_order.begin(), iteration_order.end(), g);
360 
361  iteration_orders.push_back(iteration_order);
362 #else
363  Logger::logger->warn("Creating iteration orders for parallel v1 (c_18 traversal)");
364 
365  std::vector<std::array<int, 3>> start_offsets;
366 
367  int d_x = 2;
368  int d_y = 3;
369  int d_z = 3;
370 
371  for (int x = 0; x < d_x; ++x) {
372  for (int y = 0; y < d_y; ++y) {
373  for (int z = 0; z < d_z; ++z) {
374  start_offsets.push_back({x - 1, y - 1, z - 1});
375  }
376  }
377  }
378 
379  for (size_t i = 0; i < start_offsets.size(); ++i) {
380  std::vector<Cell*> iteration_order;
381  const std::array<int, 3>& start_offset = start_offsets[i];
382 
383  for (int cx = start_offset[0]; cx <= domain_num_cells[0]; cx += d_x) {
384  for (int cy = start_offset[1]; cy <= domain_num_cells[1]; cy += d_y) {
385  for (int cz = start_offset[2]; cz <= domain_num_cells[2]; cz += d_z) {
386  iteration_order.push_back(&cells.at(cellCoordToCellIndex(cx, cy, cz)));
387  }
388  }
389  }
390 
391  iteration_orders.push_back(iteration_order);
392  }
393 #endif
394 }
395 
397  // clear the particle references in the cells
398  for (Cell& cell : cells) {
399  cell.clearParticleReferences();
400  }
401 
402  // add the particle references to the cells
403  for (Particle& p : particles) {
404  Cell* cell = particlePosToCell(p.getX());
405 
406  cell->addParticleReference(&p);
407  }
408 }
409 
411  for (Cell* cell : halo_cell_references) {
412  for (Particle* p : cell->getParticleReferences()) {
413  particles.erase(particles.begin() + (p - &particles[0]));
414  }
415  }
416 }
Class representing a cell in the linked cells algorithm.
Definition: Cell.h:12
std::vector< Cell * > & getNeighbourReferences()
Get the reference vector for the neighbouring cells.
Definition: Cell.cpp:9
void addNeighbourReference(Cell *c)
Adds a neighbour reference to the cell.
Definition: Cell.cpp:15
void addParticleReference(Particle *p)
Adds a particle reference to the cell.
Definition: Cell.cpp:11
void deleteHaloParticles()
Removes all particles in the halo cells from the particles vector. ATTENTION: A particle reference up...
std::vector< Cell * > right_halo_cell_references
References to the halo cells on the right (x = domain_num_cells[0])
void reserve(size_t n) override
Reserves space for n particles. This is useful if the number of particles is known in advance and pre...
std::vector< Cell * > bottom_boundary_cell_references
References to the boundary cells on the bottom (y = 0)
void applySimpleForces(const std::vector< std::shared_ptr< SimpleForceSource >> &simple_force_sources) override
Applies the given simple force sources to the particles.
const std::vector< Cell * > & getBoundaryCells() const
Returns the pointers of the boundary cells.
LinkedCellsContainer(const std::array< double, 3 > &domain_size, double cutoff_radius, const std::array< BoundaryCondition, 6 > &boundary_types={BoundaryCondition::OUTFLOW, BoundaryCondition::OUTFLOW, BoundaryCondition::OUTFLOW, BoundaryCondition::OUTFLOW, BoundaryCondition::OUTFLOW, BoundaryCondition::OUTFLOW}, int n=0)
Construct a new Linked Cells Particle Container object.
void applyTargettedForces(const std::vector< std::shared_ptr< TargettedForceSource >> &targetted_force_sources, double curr_simulation_time) override
Applies the given targetted force sources to the particles.
std::vector< Cell * > top_boundary_cell_references
References to the boundary cells on the top (y = domain_num_cells[1]-1)
void updateCellsParticleReferences()
Updates the particle references in the cells. This is necessary after a reallocation of the internal ...
std::vector< Cell * > domain_cell_references
References to the domain cells.
std::vector< std::vector< Cell * > > iteration_orders
void addParticle(const Particle &p) override
Adds a particle to the container.
std::vector< Cell * > boundary_cell_references
References to the boundary cells.
std::vector< Cell * > front_halo_cell_references
References to the halo cells on the front (z = domain_num_cells[2])
void initIterationOrders()
Sets the iteration orders for parallel execution.
std::vector< Cell * > bottom_halo_cell_references
References to the halo cells on the bottom (y = -1)
std::array< double, 3 > cell_size
Cell size in each dimension.
std::vector< Particle >::iterator begin() override
The begin iterator for the internal data structure.
std::array< double, 3 > domain_size
Domain size in each dimension.
std::vector< Particle >::iterator end() override
The end iterator for the internal data structure.
int cellCoordToCellIndex(int cx, int cy, int cz) const
Maps the cell coordinates to the corresponding index in the internal cell vector.
Cell * particlePosToCell(const std::array< double, 3 > &pos)
Maps the particle position to the corresponding cell index in the internal cell vector.
std::vector< Cell * > left_halo_cell_references
References to the halo cells on the left (x = -1)
Particle & operator[](int i) override
Overload of the [] operator to access the particles in the container.
const std::vector< Particle > & getParticles() const override
Returns a vector of all particles in the container.
std::array< int, 3 > domain_num_cells
Number of cells in each dimension.
std::vector< Cell > cells
Internal data structure for the cells.
const std::array< double, 3 > & getCellSize() const
Returns the cell size.
double getCutoffRadius() const
Returns the cutoff radius.
void initCells()
Populates the cell vector and sets the cells types.
void prepareForceCalculation() override
Prepares everything for the force calculations (must be called before applySimpleForces and applyPair...
const std::vector< Cell > & getCells()
Returns the cells.
void initCellNeighbourReferences()
Sets the neighbour references for each cell in the cell vector.
std::vector< Cell * > right_boundary_cell_references
References to the boundary cells on the right (x = domain_num_cells[0]-1)
std::vector< Cell * > halo_cell_references
References to the halo cells.
const std::array< double, 3 > & getDomainSize() const
Returns the domain size.
BoundaryCondition
Boundary type enum for labeling the sides of the domain.
std::vector< Cell * > top_halo_cell_references
References to the halo cells on the top (y = domain_num_cells[1])
std::vector< Cell * > left_boundary_cell_references
References to the boundary cells on the left (x = 0)
std::vector< Cell * > back_halo_cell_references
References to the halo cells on the back (z = -1)
std::vector< Cell * > back_boundary_cell_references
References to the boundary cells on the back (z = 0)
size_t size() const override
Returns the number of particles in the container.
static std::string boundaryConditionToString(const BoundaryCondition &bc)
Returns a string description of a boundary condition.
double cutoff_radius
Cutoff radius for the force calculation.
void applyPairwiseForces(const std::vector< std::shared_ptr< PairwiseForceSource >> &force_sources) override
Applies the given force sources to the particles.
std::vector< Cell * > front_boundary_cell_references
References to the boundary cells on the front (z = domain_num_cells[2]-1)
std::vector< Particle > particles
Internal data structure for the particles.
const std::array< int, 3 > & getDomainNumCells() const
Returns the number of cells in each dimension.
static std::shared_ptr< spdlog::logger > logger
Publically accessible shared pointer to the logger.
Definition: Logger.h:35
static void pre(LinkedCellsContainer &container)
Applies the preconditioning step for the outflow boundary condition.
Class to represent a particle.
Definition: Particle.h:26
bool isLocked() const
Gets whether the particle is locked in space.
Definition: Particle.h:202
void subF(const std::array< double, 3 > &force)
Subtracts a force from the particle.
Definition: Particle.h:140
const std::array< double, 3 > & getX() const
Gets the position of the particle.
Definition: Particle.h:157
void addF(const std::array< double, 3 > &force)
Adds a force to the particle.
Definition: Particle.h:129
static size_t applyBoundaryConditions(LinkedCellsContainer &container)
Applies the boundary conditions for the periodic boundary condition.
static void pre(LinkedCellsContainer &container)
Applies the preconditioning step for the periodic boundary condition.
static void applyBoundaryConditions(LinkedCellsContainer &container)
Applies the boundary conditions for the reflective boundary condition.
auto L2NormSquared(const Container &c)
Definition: ArrayUtils.h:194