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
20
31template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional = true>
33 public:
43 explicit CellFunctor(ParticleFunctor_T &f, const double sortingCutoff, DataLayoutOption dataLayout, bool useNewton3)
44 : _functor(f), _sortingCutoff(sortingCutoff), _dataLayout(dataLayout), _useNewton3(useNewton3) {
45 if constexpr (ParticleFunctor_T::supportsSoASorting) {
46 if (dataLayout == DataLayoutOption::soa) {
47 _soaThreadData.resize(autopas::autopas_get_max_threads());
48 }
49 }
50 }
51
56 void processCell(ParticleCell_T &cell);
57
65 void processCellPair(ParticleCell_T &cell1, ParticleCell_T &cell2,
66 const std::array<double, 3> &sortingDirection = {0., 0., 0.});
67
72 [[nodiscard]] DataLayoutOption::Value getDataLayout() const { return _dataLayout; }
73
78 [[nodiscard]] bool getNewton3() const { return _useNewton3; }
79
84 [[nodiscard]] bool getBidirectional() const { return bidirectional; }
85
94
102
136 [[nodiscard]] SoASortingData computeSortingData(const std::vector<std::pair<double, size_t>> &projIdxI,
137 const std::vector<std::pair<double, size_t>> &projIdxJ,
138 std::vector<size_t> &maxIndexCache,
139 std::vector<size_t> &minIndexCache) const;
140
141 private:
150 [[nodiscard]] bool shouldUseSorting(size_t particleCount, const std::array<double, 3> &sortingDirection,
151 bool useSoA) const {
152 if (sortingDirection[0] != 0.0 or sortingDirection[1] != 0.0 or sortingDirection[2] != 0.0) {
153 const auto &thresholds = useSoA ? _soaSortingThresholds : _aosSortingThresholds;
154 return particleCount >= thresholds.getThresholdByConfig(_useNewton3, sortingDirection, bidirectional);
155 }
156 return false;
157 }
158
167 [[nodiscard]] static SortingThresholdInfo2B resolveThreshold(const SortingThresholdInfoInterface &info) {
168 if (const auto *info2B = dynamic_cast<const SortingThresholdInfo2B *>(&info)) {
169 return *info2B;
170 }
171 if (const auto *infoSingle = dynamic_cast<const SortingThresholdInfoSingle *>(&info)) {
172 return SortingThresholdInfo2B(infoSingle->threshold);
173 }
174 throw utils::ExceptionHandler::AutoPasException(
175 "CellFunctor was called with wrong SortingThresholdInfo Type. (Supported are single and 2B)");
176 }
177
187 void processCellAoSImpl(ParticleCell_T &cell);
188
195 void processCellPairAoSImpl(ParticleCell_T &cell1, ParticleCell_T &cell2,
196 const std::array<double, 3> &sortingDirection);
197
205 void processCellPairSoAImpl(ParticleCell_T &cell1, ParticleCell_T &cell2,
206 const std::array<double, 3> &sortingDirection);
207
208 ParticleFunctor_T &_functor;
209
210 const double _sortingCutoff;
211
217 SortingThresholdInfo2B _aosSortingThresholds{8};
218 SortingThresholdInfo2B _soaSortingThresholds{100};
219
220 const DataLayoutOption::Value _dataLayout;
221
222 const bool _useNewton3;
223
227 struct alignas(64) SoAThreadData {
228 SoA<typename ParticleCell_T::ParticleType::SoAArraysType> sortedSoa1;
229 SoA<typename ParticleCell_T::ParticleType::SoAArraysType> sortedSoa2;
230 std::vector<std::pair<double, size_t>> projIdx1;
231 std::vector<std::pair<double, size_t>> projIdx2;
232 std::vector<size_t> maxIndex;
233 std::vector<size_t> minIndex;
234 };
235
236 std::vector<SoAThreadData> _soaThreadData{};
237};
238
239template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
241 const SortingThresholdInfoInterface &aosSortingThreshold) {
242 _aosSortingThresholds = resolveThreshold(aosSortingThreshold);
243}
244
245template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
247 const SortingThresholdInfoInterface &soaSortingThreshold) {
248 _soaSortingThresholds = resolveThreshold(soaSortingThreshold);
249}
250
251template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
253 const bool isAoS = _dataLayout == DataLayoutOption::aos;
254 const bool isSoA = _dataLayout == DataLayoutOption::soa;
255
256 // Return early if the cell is empty.
257 if ((isSoA and cell._particleSoABuffer.size() == 0) or (isAoS and cell.isEmpty())) {
258 return;
259 }
260 // Avoid force calculations if the cell contains only halo particles or if the cell is empty (=dummy)
261 if (not cell.canHaveOwnedParticles()) {
262 return;
263 }
264
265 if (isAoS) {
266 processCellAoSImpl(cell);
267 } else if (isSoA) {
268 _functor.SoAFunctorSingle(cell._particleSoABuffer, _useNewton3);
269 }
270}
271
272template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
274 ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array<double, 3> &sortingDirection) {
275 const bool isAoS = _dataLayout == DataLayoutOption::aos;
276 const bool isSoA = _dataLayout == DataLayoutOption::soa;
277
278 // Return early if a cell is empty.
279 if ((isSoA and (cell1._particleSoABuffer.size() == 0 or cell2._particleSoABuffer.size() == 0)) or
280 (isAoS and (cell1.isEmpty() or cell2.isEmpty()))) {
281 return;
282 }
283
284 if (not cell1.canHaveOwnedParticles()) {
285 // Nothing to do if cell1 has no owned particles and we don't write to cell2 particles.
286 if constexpr (not bidirectional) {
287 if (not _useNewton3) {
288 return;
289 }
290 }
291 // Nothing to do if both cells cannot have owned particles.
292 if (not cell2.canHaveOwnedParticles()) {
293 return;
294 }
295 }
296
297 if (isAoS) {
298 processCellPairAoSImpl(cell1, cell2, sortingDirection);
299 } else if (isSoA) {
300 processCellPairSoAImpl(cell1, cell2, sortingDirection);
301 }
302}
303
304template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
306 // helper function
307 const auto interactParticles = [this](auto &p1, auto &p2) {
308 this->_functor.AoSFunctor(p1, p2, this->_useNewton3);
309
310 if (not this->_useNewton3) {
311 this->_functor.AoSFunctor(p2, p1, false);
312 }
313 };
314 // @todo: Which parameter from the Thresholds is used for inner cell sorting is currently arbitrary, think of a good
315 // way to handle this
316 if (cell.size() >= _aosSortingThresholds.getThresholdByConfig(_useNewton3, {1, 0, 0}, bidirectional)) {
317 SortedCellView<ParticleCell_T> cellSorted(cell, utils::ArrayMath::normalize(cell.getCellLength()));
318
319 for (auto cellIter1 = cellSorted._particles.begin(); cellIter1 != cellSorted._particles.end(); ++cellIter1) {
320 auto &[p1Projection, p1Ptr] = *cellIter1;
321 // start inner loop ahead of the outer loop
322 for (auto cellIter2 = std::next(cellIter1); cellIter2 != cellSorted._particles.end(); ++cellIter2) {
323 auto &[p2Projection, p2Ptr] = *cellIter2;
324 if (std::abs(p2Projection - p1Projection) > _sortingCutoff) {
325 break;
326 }
327 interactParticles(*p1Ptr, *p2Ptr);
328 }
329 }
330 } else {
331 for (auto p1Ptr = cell.begin(); p1Ptr != cell.end(); ++p1Ptr) {
332 auto p2Ptr = p1Ptr;
333 ++p2Ptr;
334 for (; p2Ptr != cell.end(); ++p2Ptr) {
335 interactParticles(*p1Ptr, *p2Ptr);
336 }
337 }
338 }
339}
340
341template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
342void CellFunctor<ParticleCell_T, ParticleFunctor_T, bidirectional>::processCellPairAoSImpl(
343 ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array<double, 3> &sortingDirection) {
344 const auto interactParticles = [this](auto &p1, auto &p2) {
345 this->_functor.AoSFunctor(p1, p2, this->_useNewton3);
346 if constexpr (bidirectional) {
347 if (not this->_useNewton3) {
348 this->_functor.AoSFunctor(p2, p1, false);
349 }
350 }
351 };
352
353 if (shouldUseSorting(cell1.size() + cell2.size(), sortingDirection, /*useSoA*/ false)) {
354 // Use sorted cell views
355 SortedCellView<ParticleCell_T> cell1Sorted(cell1, sortingDirection);
356 SortedCellView<ParticleCell_T> cell2Sorted(cell2, sortingDirection);
357
358 for (auto &[p1Projection, p1Ptr] : cell1Sorted._particles) {
359 for (auto &[p2Projection, p2Ptr] : cell2Sorted._particles) {
360 if (std::abs(p2Projection - p1Projection) > _sortingCutoff) {
361 break;
362 }
363
364 interactParticles(*p1Ptr, *p2Ptr);
365 }
366 }
367 } else {
368 // Without sorting
369 for (auto &p1 : cell1) {
370 for (auto &p2 : cell2) {
371 interactParticles(p1, p2);
372 }
373 }
374 }
375}
376
377template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
379 const std::vector<std::pair<double, size_t>> &projIdxI, const std::vector<std::pair<double, size_t>> &projIdxJ,
380 std::vector<size_t> &maxIndexCache, std::vector<size_t> &minIndexCache) const {
381 const size_t nI = projIdxI.size();
382 const size_t nJ = projIdxJ.size();
383
384 // No j-particles to interact with: skip all i-particles. Guards the projIdxJ[0] access below, which would
385 // otherwise be an out-of-bounds read if projIdxJ were empty.
386 if (nJ == 0) {
387 maxIndexCache.assign(nI, 0);
388 minIndexCache.assign(nI, 0);
389 return {nI, nI, maxIndexCache, minIndexCache};
390 }
391
392 // Compute startI: the first i-particle that can interact with any j-particle.
393 // Any i with `projI[i] < projJ[0] - cutoff` is strictly farther than cutoff from every j along the
394 // sorting axis, so it cannot contribute an interaction and is skipped. Particles exactly at the cutoff
395 // distance are kept, consistent with the inclusive cutoff test used below for minIndex/maxIndex.
396 const double startThreshold = projIdxJ[0].first - _sortingCutoff;
397 auto startIter = std::lower_bound(projIdxI.begin(), projIdxI.end(), startThreshold,
398 [](const auto &elem, double val) { return elem.first < val; });
399 const size_t startI = static_cast<size_t>(startIter - projIdxI.begin());
400
401 // Compute endI: the exclusive upper bound of i-particles that can interact with any j-particle. Symmetric
402 // to startI: any i with `projI[i] > projJ.back() + cutoff` is strictly farther than cutoff from every j
403 // along the sorting axis, so it and all following i (projIdxI is sorted) can be skipped unconditionally.
404 const double endThreshold = projIdxJ[nJ - 1].first + _sortingCutoff;
405 auto endIter = std::upper_bound(projIdxI.begin(), projIdxI.end(), endThreshold,
406 [](double val, const auto &elem) { return val < elem.first; });
407 const size_t endI = static_cast<size_t>(endIter - projIdxI.begin());
408
409 // Compute maxIndexCache and minIndexCache in a single O(nI + nJ) sweep.
410 // Both bounds are monotonically non-decreasing with i because projIdxI is sorted, so each pointer only
411 // advances, this includes some particles redundant for calculation but avoids a O(n^2) scan.
412 // For each i, the j-particles in [minIndex, maxIndex) are the candidates satisfying the 1-D cutoff check.
413 maxIndexCache.assign(nI, 0);
414 minIndexCache.assign(nI, 0);
415 size_t jUpper = 0, jLower = 0;
416 for (size_t i = startI; i < endI; ++i) {
417 // jUpper: first j where projJ > projI[i] + cutoff (exclusive upper bound).
418 while (jUpper < nJ and projIdxJ[jUpper].first <= projIdxI[i].first + _sortingCutoff) {
419 ++jUpper;
420 }
421 maxIndexCache[i] = jUpper;
422 // jLower: first j where projJ >= projI[i] - cutoff (lower bound).
423 while (jLower < nJ and projIdxJ[jLower].first < projIdxI[i].first - _sortingCutoff) {
424 ++jLower;
425 }
426 minIndexCache[i] = jLower;
427 }
428
429 return {startI, endI, maxIndexCache, minIndexCache};
430}
431
432template <class ParticleCell_T, class ParticleFunctor_T, bool bidirectional>
434 ParticleCell_T &cell1, ParticleCell_T &cell2, const std::array<double, 3> &sortingDirection) {
435 if constexpr (ParticleFunctor_T::supportsSoASorting) {
436 if (shouldUseSorting(cell1._particleSoABuffer.size() + cell2._particleSoABuffer.size(), sortingDirection,
437 /*useSoA*/ true)) {
438 using Particle_T = ParticleCell_T::ParticleType;
439 auto &threadData = _soaThreadData[autopas::autopas_get_thread_num()];
440
441 SortedSoAView<Particle_T, ParticleFunctor_T> view1(cell1._particleSoABuffer, sortingDirection,
442 threadData.sortedSoa1, threadData.projIdx1);
443 SortedSoAView<Particle_T, ParticleFunctor_T> view2(cell2._particleSoABuffer, sortingDirection,
444 threadData.sortedSoa2, threadData.projIdx2);
445
446 _functor.SoAFunctorPairSorted(
447 view1.getView(), view2.getView(),
448 computeSortingData(threadData.projIdx1, threadData.projIdx2, threadData.maxIndex, threadData.minIndex),
449 _useNewton3);
450
451 if constexpr (bidirectional) {
452 if (not _useNewton3) {
453 // Roles swapped relative to the call above: startI/maxIndex are now trivial (0 / nJ), endI/minIndex do
454 // the pruning instead.
455 _functor.SoAFunctorPairSorted(
456 view2.getView(), view1.getView(),
457 computeSortingData(threadData.projIdx2, threadData.projIdx1, threadData.maxIndex, threadData.minIndex),
458 false);
459 }
460 }
461
462 view1.scatterBack();
463 // if we are not bidirectional and don't use newton3 we don't need to scatter back the second view
464 if constexpr (bidirectional) {
465 view2.scatterBack();
466 } else if (_useNewton3) {
467 view2.scatterBack();
468 }
469 return;
470 }
471 }
472 _functor.SoAFunctorPair(cell1._particleSoABuffer, cell2._particleSoABuffer, _useNewton3);
473 if constexpr (bidirectional) {
474 if (not _useNewton3) {
475 _functor.SoAFunctorPair(cell2._particleSoABuffer, cell1._particleSoABuffer, false);
476 }
477 }
478}
479} // namespace autopas::internal
A sorted view on a SoA buffer.
Definition: SortedSoAView.h:34
A cell functor.
Definition: CellFunctor.h:32
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:273
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:378
bool getNewton3() const
Getter.
Definition: CellFunctor.h:78
void setAoSSortingThresholds(const SortingThresholdInfoInterface &aosSortingThreshold)
Set the aos-sorting-threshold.
Definition: CellFunctor.h:240
bool getBidirectional() const
Getter.
Definition: CellFunctor.h:84
void processCell(ParticleCell_T &cell)
Process the interactions inside one cell.
Definition: CellFunctor.h:252
DataLayoutOption::Value getDataLayout() const
Getter.
Definition: CellFunctor.h:72
void setSoASortingThresholds(const SortingThresholdInfoInterface &soaSortingThreshold)
Set the SoA sorting-threshold.
Definition: CellFunctor.h:246
CellFunctor(ParticleFunctor_T &f, const double sortingCutoff, DataLayoutOption dataLayout, bool useNewton3)
The constructor of CellFunctor.
Definition: CellFunctor.h:43
This namespace is used for implementation specifics.
Definition: CellFunctor.h:21
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 pair of SoA buffers, soa1 as the outer (i) loop a...
Definition: PairwiseFunctor.h:33
Per-Newton3-state, per-SortingDirectionOption pair-sorting thresholds for a 2-body CellFunctor.
Definition: SortingThresholdInfo2B.h:21
size_t getThresholdByConfig(bool newton3, std::array< double, 3 > sortingDirection, bool bidirectional) const
Getter to automatically get the correct threshold based on current newton3/CellDirection/bidirectiona...
Definition: SortingThresholdInfo2B.h:113
Polymorphic base for sorting-threshold storage.
Definition: SortingThresholdInfoInterface.h:18