AutoPas  3.0.0
Loading...
Searching...
No Matches
CellFunctor.h
Go to the documentation of this file.
1
8#pragma once
9
10#include <algorithm>
11
17
28template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional = true>
30 public:
40 explicit CellFunctor(ParticleFunctor_T &f, const double sortingCutoff, DataLayoutOption dataLayout, bool useNewton3)
41 : _functor(f), _sortingCutoff(sortingCutoff), _dataLayout(dataLayout), _useNewton3(useNewton3) {
42 if (dataLayout == DataLayoutOption::soa) {
43 _soaThreadData.resize(autopas::autopas_get_max_threads());
44 }
45 }
46
51 void processCell(ParticleCell_T &cell);
52
60 void processCellPair(ParticleCell_T &cell1, ParticleCell_T &cell2,
61 const std::array<double, 3> &sortingDirection = {0., 0., 0.});
62
67 [[nodiscard]] DataLayoutOption::Value getDataLayout() const { return _dataLayout; }
68
73 [[nodiscard]] bool getNewton3() const { return _useNewton3; }
74
79 [[nodiscard]] bool getBidirectional() const { return bidirectional; }
80
87 void setAoSSortingThreshold(size_t aosSortingThreshold);
88
95 void setSoASortingThreshold(size_t soaSortingThreshold);
96
123 [[nodiscard]] SoASortingData computeSortingData(const std::vector<std::pair<double, size_t>> &projIdxI,
124 const std::vector<std::pair<double, size_t>> &projIdxJ,
125 std::vector<size_t> &maxIndexCache,
126 std::vector<size_t> &minIndexCache) const;
127
128 private:
135 [[nodiscard]] bool shouldUseAoSSorting(size_t particleCount, const std::array<double, 3> &sortingDirection) const {
136 return particleCount >= _aosSortingThreshold and
137 (sortingDirection[0] != 0.0 or sortingDirection[1] != 0.0 or sortingDirection[2] != 0.0);
138 }
139
146 [[nodiscard]] bool shouldUseSoASorting(size_t particleCount, const std::array<double, 3> &sortingDirection) const {
147 return particleCount >= _soaSortingThreshold and
148 (sortingDirection[0] != 0.0 or sortingDirection[1] != 0.0 or sortingDirection[2] != 0.0);
149 }
150
160 void processCellAoSImpl(ParticleCell_T &cell);
161
168 void processCellPairAoSImpl(ParticleCell_T &cell1, ParticleCell_T &cell2,
169 const std::array<double, 3> &sortingDirection);
170
178 void processCellPairSoAImpl(ParticleCell_T &cell1, ParticleCell_T &cell2,
179 const std::array<double, 3> &sortingDirection);
180
181 ParticleFunctor_T &_functor;
182
183 const double _sortingCutoff;
184
189 size_t _aosSortingThreshold{8};
190
195 size_t _soaSortingThreshold{50};
196
197 const DataLayoutOption::Value _dataLayout;
198
199 const bool _useNewton3;
200
204 struct alignas(64) SoAThreadData {
205 SoA<typename ParticleCell_T::ParticleType::SoAArraysType> sortedSoa1;
206 SoA<typename ParticleCell_T::ParticleType::SoAArraysType> sortedSoa2;
207 std::vector<std::pair<double, size_t>> projIdx1;
208 std::vector<std::pair<double, size_t>> projIdx2;
209 std::vector<size_t> maxIndex;
210 std::vector<size_t> minIndex;
211 };
212
213 std::vector<SoAThreadData> _soaThreadData{};
214};
215
216template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
218 _aosSortingThreshold = aosSortingThreshold;
219}
220
221template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
223 _soaSortingThreshold = soaSortingThreshold;
224}
225
226template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
228 const bool isAoS = _dataLayout == DataLayoutOption::aos;
229 const bool isSoA = _dataLayout == DataLayoutOption::soa;
230
231 // Return early if the cell is empty.
232 if ((isSoA and cell._particleSoABuffer.size() == 0) or (isAoS and cell.isEmpty())) {
233 return;
234 }
235 // Avoid force calculations if the cell contains only halo particles or if the cell is empty (=dummy)
236 if (not cell.canHaveOwnedParticles()) {
237 return;
238 }
239
240 if (isAoS) {
241 processCellAoSImpl(cell);
242 } else if (isSoA) {
243 _functor.SoAFunctorSingle(cell._particleSoABuffer, _useNewton3);
244 }
245}
246
247template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
249 ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array<double, 3> &sortingDirection) {
250 const bool isAoS = _dataLayout == DataLayoutOption::aos;
251 const bool isSoA = _dataLayout == DataLayoutOption::soa;
252
253 // Return early if a cell is empty.
254 if ((isSoA and (cell1._particleSoABuffer.size() == 0 or cell2._particleSoABuffer.size() == 0)) or
255 (isAoS and (cell1.isEmpty() or cell2.isEmpty()))) {
256 return;
257 }
258
259 if (not cell1.canHaveOwnedParticles()) {
260 // Nothing to do if cell1 has no owned particles and we don't write to cell2 particles.
261 if constexpr (not bidirectional) {
262 if (not _useNewton3) {
263 return;
264 }
265 }
266 // Nothing to do if both cells cannot have owned particles.
267 if (not cell2.canHaveOwnedParticles()) {
268 return;
269 }
270 }
271
272 if (isAoS) {
273 processCellPairAoSImpl(cell1, cell2, sortingDirection);
274 } else if (isSoA) {
275 processCellPairSoAImpl(cell1, cell2, sortingDirection);
276 }
277}
278
279template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
281 // helper function
282 const auto interactParticles = [this](auto &p1, auto &p2) {
283 this->_functor.AoSFunctor(p1, p2, this->_useNewton3);
284
285 if (not this->_useNewton3) {
286 this->_functor.AoSFunctor(p2, p1, false);
287 }
288 };
289
290 if (cell.size() >= _aosSortingThreshold) {
291 SortedCellView<ParticleCell_T> cellSorted(cell, utils::ArrayMath::normalize(cell.getCellLength()));
292
293 for (auto cellIter1 = cellSorted._particles.begin(); cellIter1 != cellSorted._particles.end(); ++cellIter1) {
294 auto &[p1Projection, p1Ptr] = *cellIter1;
295 // start inner loop ahead of the outer loop
296 for (auto cellIter2 = std::next(cellIter1); cellIter2 != cellSorted._particles.end(); ++cellIter2) {
297 auto &[p2Projection, p2Ptr] = *cellIter2;
298 if (std::abs(p2Projection - p1Projection) > _sortingCutoff) {
299 break;
300 }
301 interactParticles(*p1Ptr, *p2Ptr);
302 }
303 }
304 } else {
305 for (auto p1Ptr = cell.begin(); p1Ptr != cell.end(); ++p1Ptr) {
306 auto p2Ptr = p1Ptr;
307 ++p2Ptr;
308 for (; p2Ptr != cell.end(); ++p2Ptr) {
309 interactParticles(*p1Ptr, *p2Ptr);
310 }
311 }
312 }
313}
314
315template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
316void CellFunctor<ParticleCell_T, ParticleFunctor_T, bidirectional>::processCellPairAoSImpl(
317 ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array<double, 3> &sortingDirection) {
318 const auto interactParticles = [this](auto &p1, auto &p2) {
319 this->_functor.AoSFunctor(p1, p2, this->_useNewton3);
320 if constexpr (bidirectional) {
321 if (not this->_useNewton3) {
322 this->_functor.AoSFunctor(p2, p1, false);
323 }
324 }
325 };
326
327 if (shouldUseAoSSorting(cell1.size() + cell2.size(), sortingDirection)) {
328 // Use sorted cell views
329 SortedCellView<ParticleCell_T> cell1Sorted(cell1, sortingDirection);
330 SortedCellView<ParticleCell_T> cell2Sorted(cell2, sortingDirection);
331
332 for (auto &[p1Projection, p1Ptr] : cell1Sorted._particles) {
333 for (auto &[p2Projection, p2Ptr] : cell2Sorted._particles) {
334 if (std::abs(p2Projection - p1Projection) > _sortingCutoff) {
335 break;
336 }
337
338 interactParticles(*p1Ptr, *p2Ptr);
339 }
340 }
341 } else {
342 // Without sorting
343 for (auto &p1 : cell1) {
344 for (auto &p2 : cell2) {
345 interactParticles(p1, p2);
346 }
347 }
348 }
349}
350
351template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
353 const std::vector<std::pair<double, size_t>> &projIdxI, const std::vector<std::pair<double, size_t>> &projIdxJ,
354 std::vector<size_t> &maxIndexCache, std::vector<size_t> &minIndexCache) const {
355 const size_t nI = projIdxI.size();
356 const size_t nJ = projIdxJ.size();
357
358 // No j-particles to interact with: skip all i-particles. Guards the projIdxJ[0] access below, which would
359 // otherwise be an out-of-bounds read if projIdxJ were empty.
360 if (nJ == 0) {
361 maxIndexCache.assign(nI, 0);
362 minIndexCache.assign(nI, 0);
363 return {nI, maxIndexCache, minIndexCache};
364 }
365
366 // Compute startI: the first i-particle that can interact with any j-particle.
367 // Any i with `projI[i] < projJ[0] - cutoff` is strictly farther than cutoff from every j along the
368 // sorting axis, so it cannot contribute an interaction and is skipped. Particles exactly at the cutoff
369 // distance are kept, consistent with the inclusive cutoff test used below for minIndex/maxIndex.
370 const double threshold = projIdxJ[0].first - _sortingCutoff;
371 auto startIter = std::lower_bound(projIdxI.begin(), projIdxI.end(), threshold,
372 [](const auto &elem, double val) { return elem.first < val; });
373 const size_t startI = static_cast<size_t>(startIter - projIdxI.begin());
374
375 // Compute maxIndexCache and minIndexCache in a single O(nI + nJ) sweep.
376 // Both bounds are monotonically non-decreasing with i because projIdxI is sorted, so each pointer only
377 // advances, this includes some particles redundant for calculation but avoids a O(n^2) scan.
378 // For each i, the j-particles in [minIndex, maxIndex) are the candidates satisfying the 1-D cutoff check.
379 maxIndexCache.assign(nI, 0);
380 minIndexCache.assign(nI, 0);
381 size_t jUpper = 0, jLower = 0;
382 for (size_t i = startI; i < nI; ++i) {
383 // jUpper: first j where projJ > projI[i] + cutoff (exclusive upper bound).
384 while (jUpper < nJ and projIdxJ[jUpper].first <= projIdxI[i].first + _sortingCutoff) {
385 ++jUpper;
386 }
387 maxIndexCache[i] = jUpper;
388 // jLower: first j where projJ >= projI[i] - cutoff (lower bound).
389 while (jLower < nJ and projIdxJ[jLower].first < projIdxI[i].first - _sortingCutoff) {
390 ++jLower;
391 }
392 minIndexCache[i] = jLower;
393 }
394
395 return {startI, maxIndexCache, minIndexCache};
396}
397
398template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
400 ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array<double, 3> &sortingDirection) {
401 if constexpr (ParticleFunctor_T::supportsSoASorting) {
402 if (shouldUseSoASorting(cell1._particleSoABuffer.size() + cell2._particleSoABuffer.size(), sortingDirection)) {
403 using Particle_T = ParticleCell_T::ParticleType;
404 auto &threadData = _soaThreadData[autopas::autopas_get_thread_num()];
405
406 SortedSoAView<Particle_T, ParticleFunctor_T> view1(cell1._particleSoABuffer, sortingDirection,
407 threadData.sortedSoa1, threadData.projIdx1);
408 SortedSoAView<Particle_T, ParticleFunctor_T> view2(cell2._particleSoABuffer, sortingDirection,
409 threadData.sortedSoa2, threadData.projIdx2);
410
411 _functor.SoAFunctorPairSorted(
412 view1.getView(), view2.getView(),
413 computeSortingData(threadData.projIdx1, threadData.projIdx2, threadData.maxIndex, threadData.minIndex),
414 _useNewton3);
415
416 if constexpr (bidirectional) {
417 if (not _useNewton3) {
418 _functor.SoAFunctorPairSorted(
419 view2.getView(), view1.getView(),
420 computeSortingData(threadData.projIdx2, threadData.projIdx1, threadData.maxIndex, threadData.minIndex),
421 false);
422 }
423 }
424
425 view1.scatterBack();
426 // if we are not bidirectional and don't use newton3 we don't need to scatter back the second view
427 if constexpr (bidirectional) {
428 view2.scatterBack();
429 } else if (_useNewton3) {
430 view2.scatterBack();
431 }
432 return;
433 }
434 }
435 _functor.SoAFunctorPair(cell1._particleSoABuffer, cell2._particleSoABuffer, _useNewton3);
436 if constexpr (bidirectional) {
437 if (not _useNewton3) {
438 _functor.SoAFunctorPair(cell2._particleSoABuffer, cell1._particleSoABuffer, false);
439 }
440 }
441}
442} // namespace autopas::internal
A sorted view on a SoA buffer.
Definition: SortedSoAView.h:34
A cell functor.
Definition: CellFunctor.h:29
void setSoASortingThreshold(size_t soaSortingThreshold)
Set the SoA sorting-threshold.
Definition: CellFunctor.h:222
void processCellPair(ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array< double, 3 > &sortingDirection={0., 0., 0.})
Process the interactions between the particles of cell1 with particles of cell2.
Definition: CellFunctor.h:248
SoASortingData computeSortingData(const std::vector< std::pair< double, size_t > > &projIdxI, const std::vector< std::pair< double, size_t > > &projIdxJ, std::vector< size_t > &maxIndexCache, std::vector< size_t > &minIndexCache) const
Computes conservative per-particle index bounds into projIdxJ based on a 1-D projection cutoff check.
Definition: CellFunctor.h:352
bool getNewton3() const
Getter.
Definition: CellFunctor.h:73
bool getBidirectional() const
Getter.
Definition: CellFunctor.h:79
void processCell(ParticleCell_T &cell)
Process the interactions inside one cell.
Definition: CellFunctor.h:227
void setAoSSortingThreshold(size_t aosSortingThreshold)
Set the aos-sorting-threshold.
Definition: CellFunctor.h:217
DataLayoutOption::Value getDataLayout() const
Getter.
Definition: CellFunctor.h:67
CellFunctor(ParticleFunctor_T &f, const double sortingCutoff, DataLayoutOption dataLayout, bool useNewton3)
The constructor of CellFunctor.
Definition: CellFunctor.h:40
This namespace is used for implementation specifics.
Definition: CellFunctor.h:18
constexpr std::array< T, SIZE > normalize(const std::array< T, SIZE > &a)
Generates a normalized array (|a| = 1).
Definition: ArrayMath.h:304
int autopas_get_max_threads()
Dummy for omp_get_max_threads() when no OpenMP is available.
Definition: WrapOpenMP.h:144
int autopas_get_thread_num()
Dummy for omp_set_lock() when no OpenMP is available.
Definition: WrapOpenMP.h:132
Precomputed index bounds for iterating a pre-sorted SoA pair.
Definition: PairwiseFunctor.h:30