Commit ff87d40d authored by Christoph Rettinger's avatar Christoph Rettinger
Browse files

Merge branch 'mr_hashgrids_for_mesapd' into 'master'

HashGrids for mesapd

See merge request !457
parents f3b6784e c73d7a82
Pipeline #32682 passed with stages
in 591 minutes and 10 seconds
...@@ -90,6 +90,7 @@ if __name__ == '__main__': ...@@ -90,6 +90,7 @@ if __name__ == '__main__':
cs.add_property("diag_n_inv", "real_t", defValue="real_t(0)") cs.add_property("diag_n_inv", "real_t", defValue="real_t(0)")
cs.add_property("p", "walberla::mesa_pd::Vec3", defValue="real_t(0)") cs.add_property("p", "walberla::mesa_pd::Vec3", defValue="real_t(0)")
mpd.add(data.HashGrids())
mpd.add(data.LinkedCells()) mpd.add(data.LinkedCells())
mpd.add(data.SparseLinkedCells()) mpd.add(data.SparseLinkedCells())
mpd.add(data.ShapeStorage(ps)) mpd.add(data.ShapeStorage(ps))
......
# -*- coding: utf-8 -*-
from ..utility import generate_file
class HashGrids:
def generate(self, module):
ctx = {'module': module}
generate_file(module['module_path'], 'data/HashGrids.templ.h', ctx)
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
from .ContactHistory import ContactHistory from .ContactHistory import ContactHistory
from .ContactStorage import ContactStorage from .ContactStorage import ContactStorage
from .HashGrids import HashGrids
from .LinkedCells import LinkedCells from .LinkedCells import LinkedCells
from .ParticleStorage import ParticleStorage from .ParticleStorage import ParticleStorage
from .ShapeStorage import ShapeStorage from .ShapeStorage import ShapeStorage
...@@ -9,6 +10,7 @@ from .SparseLinkedCells import SparseLinkedCells ...@@ -9,6 +10,7 @@ from .SparseLinkedCells import SparseLinkedCells
__all__ = ['ContactHistory', __all__ = ['ContactHistory',
'ContactStorage', 'ContactStorage',
'HashGrids',
'LinkedCells', 'LinkedCells',
'ParticleStorage', 'ParticleStorage',
'ShapeStorage', 'ShapeStorage',
......
//======================================================================================================================
//
// This file is part of waLBerla. waLBerla is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// waLBerla is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
//
//! \file
//! \author Christoph Rettinger christoph.rettinger@fau.de>
//
//======================================================================================================================
//======================================================================================================================
//
// THIS FILE IS GENERATED - PLEASE CHANGE THE TEMPLATE !!!
//
//======================================================================================================================
#pragma once
#include <mesa_pd/data/DataTypes.h>
#include <mesa_pd/data/IAccessor.h>
#include <mesa_pd/data/ParticleStorage.h>
#include <core/Abort.h>
#include <core/debug/Debug.h>
#include <atomic>
#include <cmath>
#include <vector>
#include <array>
namespace walberla {
namespace mesa_pd {
namespace data {
//*************************************************************************************************
/*!\brief Implementation of the 'Hierarchical Hash Grids' coarse collision detection algorithm.
*
* This is a port of the pe implementation (src/pe/ccd/HashGrids.h) for mesa_pd.
*
* The 'Hierarchical Hash Grids' coarse collision detection algorithm is based on a spatial
* partitioning scheme that uses a hierarchy of uniform, hash storage based grids. Uniform grids
* subdivide the simulation space into cubic cells. A hierarchy of spatially superimposed grids
* provides a set of grids with each grid subdividing the very same space, however possessing
* different and thus unique-sized cells. Spatial partitioning is achieved by assigning every
* rigid particle to exactly one cell in exactly one grid - that is the grid with the smallest cells
* that are larger than the rigid particle (more precisely cells that are larger than the longest
* edge of the rigid particle's axis-aligned bounding box). As a consequence, the collision detection
* is reduced to only checking particle's that are assigned to spatially directly adjacent cells.
*
* Key features of this implementation are not only an <b>average-case computational complexity
* of order O(N)</b> as well as a space complexity of order O(N), but also a short actual runtime
* combined with low memory consumption. Moreover, the data structure representing the hierarchy
* of grids has the ability to, at runtime, automatically and perfectly adapt to the particles of the
* underlying simulation. Also, arbitrary particles can be added and removed in constant time, O(1).
* Consequently, the coarse collision detection algorithm based on these hierarchical hash grids
* is <b>especially well-suited for large-scale simulations</b> involving very large numbers of
* particles.
*
* For further information and a much more detailed explanation of this algorithm see
* https://www10.cs.fau.de/publications/theses/2009/Schornbaum_SA_2009.pdf
*/
class HashGrids
{
public:
//=================================================================================================
//
// CONSTANTS
//
//=================================================================================================
//*************************************************************************************************
/*!\brief The initial number of cells in x-direction of a newly created hash grid.
*
* This value represents the initial number of cells of a newly created hash grid in x-direction.
* The larger the value (i.e. the greater the number of cells of every newly created hash grid),
* the more memory is required for the storage of the hash grid. Since the size of a hash grid is
* increased at runtime in order to adapt to the number of currently inserted particles, 16x16x16
* is a suitable choice for the initial size of a newly created hash grid - it already consists
* of four thousand cells, yet only requires a few hundred kilobytes of memory. Note that the
* initial number of cells must both be greater-or-equal to 4 and equal to a power of two. Also
* note that the initial number of cells does not necessarily have to be equal for all three
* coordinate directions.
*/
static constexpr size_t xCellCount = 16;
//*************************************************************************************************
//*************************************************************************************************
/*!\brief The initial number of cells in y-direction of a newly created hash grid.
*
* See HashGrids::xCellCount for more infos.
*/
static constexpr size_t yCellCount = 16;
//*************************************************************************************************
//*************************************************************************************************
/*!\brief The initial number of cells in z-direction of a newly created hash grid.
*
* See HashGrids::xCellCount for more infos.
*/
static constexpr size_t zCellCount = 16;
//*************************************************************************************************
//*************************************************************************************************
/*!\brief The initial storage capacity of a newly created grid cell particle container.
*
* This value specifies the initial storage capacity reserved for every grid cell particle container,
* i.e., the number of particles that can initially be assigned to a grid cell with the need to
* increase the storage capacity. The smaller this number, the more likely the storage capacity
* of a particle container must be increased, leading to potentially costly reallocation operations,
* which generally involve the entire storage space to be copied to a new location. The greater
* this number, the more memory is required. Rule of thumb:
*
* \f$ cellVectorSize = 2 \cdot hierarchyFactor^3 \f$
*/
static constexpr size_t cellVectorSize = 16;
//*************************************************************************************************
//*************************************************************************************************
/*!\brief The initial storage capacity of the grid-global vector.
*
* This value specifies the initial storage capacity of the grid-global vector that keeps track
* of all particle-occupied cells. As long as at least one particle is assigned to a certain cell, this
* cell is recorded in a grid-global list that keeps track of all particle-occupied cells in order to
* avoid iterating through all grid cells whenever all particles that are stored in the grid need
* to be addressed.
*/
static constexpr size_t occupiedCellsVectorSize = 256;
//*************************************************************************************************
//*************************************************************************************************
/*!\brief The minimal ratio of cells to particles that must be maintained at any time.
*
* This \a minimalGridDensity specifies the minimal ratio of cells to particles that is allowed
* before a grid grows.\n
* In order to handle an initially unknown and ultimately arbitrary number of particles, each hash
* grid, starting with a rather small number of cells at the time of its creation, must have the
* ability to grow as new particles are inserted. Therefore, if by inserting a particle into a hash grid
* the associated grid density - that is the ratio of cells to particles - drops below the threshold
* specified by \a minimalGridDensity, the number of cells in each coordinate direction is doubled
* (thus the total number of grid cells is increased by a factor of 8).
*
* Possible settings: any integral value greater than 0.
*/
static constexpr size_t minimalGridDensity = 8;
//*************************************************************************************************
//*************************************************************************************************
/*!\brief The constant factor by which the cell size of any two successive grids differs.
*
* This factor specifies the size difference of two successive grid levels of the hierarchical
* hash grids. The grid hierarchy is constructed such that the cell size of any two successive
* grids differs by a constant factor - the hierarchy factor \a hierarchyFactor. As a result,
* the cell size \f$ c_k \f$ of grid \f$ k \f$ can be expressed as:
*
* \f$ c_k = c_0 \cdot hierarchyFactor^k \f$.
*
* Note that the hierarchy does not have to be dense, which means, if not every valid cell size
* that can be generated is required, some in-between grids are not created. Consequently, the
* cell size of two successive grids differs by a factor of \f$ hierarchyFactor^x \f$, with x
* being an integral value that is not necessarily equal to 1.
*
* The larger the ratio between the cell size of two successive grids, the more particles are
* potentially assigned to one single cell, but overall fewer grids have to be used. On the other
* hand, the smaller the ratio between the cell size of two successive grids, the fewer particles
* are assigned to one single cell, but overall more grids have to be created. Hence, the number
* of particles that are stored in one single cell is inversely proportional to the number of grids
* which are in use. Unfortunately, minimizing the number of particles that are potentially assigned
* to the same cell and at the same time also minimizing the number of grids in the hierarchy are
* two opposing goals. In general - based on the evaluation of a number of different scenarios -
* the best choice seems to be a hierarchy factor that is equal to 2.0.
*
* Possible settings: any floating point value that is greater than 1.0.
*/
static constexpr real_t hierarchyFactor = real_t(2);
//*************************************************************************************************
private:
//**Type definitions****************************************************************************
//! Vector for storing (handles to) rigid particles.
using ParticleIdxVector = std::vector<size_t>;
//**********************************************************************************************
//**********************************************************************************************
/*!\brief Implementation of the hash grid data structure.
*/
class HashGrid
{
private:
//**Type definitions*************************************************************************
//! The signed integer type that is used for storing offsets to neighboring cells.
using offset_t = long;
//*******************************************************************************************
//*******************************************************************************************
/*!\brief Data structure for representing a cell in the hash grid (used by the 'Hierarchical
// Hash Grids' coarse collision detection algorithm).
*/
struct Cell
{
ParticleIdxVector* particles_; /*!< \brief The cell's particle container that stores (handles to)
all the particles that are assigned to this cell. */
/*!< Note that only a pointer to such a particle container is
stored: in order to save memory, this container object
is only allocated as long as there are particles assigned
to this cell. */
offset_t* neighborOffset_; /*!< \brief Pointer to an array that is storing offsets that
can be used to directly access all the neighboring
cells in the hash grid. */
size_t occupiedCellsId_; //!< The cell's index in the \a occupiedCells_ vector.
};
//*******************************************************************************************
//**Type definitions*************************************************************************
//! Vector for storing pointers to (particle-occupied) cells.
using CellVector = std::vector<Cell *>;
//*******************************************************************************************
public:
explicit HashGrid( real_t cellSpan );
~HashGrid();
real_t getCellSpan() const { return cellSpan_; }
template <typename Accessor>
inline void addParticle( size_t p_idx, Accessor& ac );
template <typename Selector, typename Accessor, typename Func, typename... Args>
void checkEachParticlePairHalfAndStore( ParticleIdxVector& particlesOnGrid,
const bool openmp, const Selector& selector, Accessor& ac, Func&& func, Args&&... args ) const;
template <typename Selector, typename Accessor, typename Func, typename... Args>
void checkAgainstVectorEachParticlePairHalf( const ParticleIdxVector& particlesOnGrid,
const bool openmp, const Selector& selector, Accessor& ac, Func&& func, Args&&... args ) const;
void clear();
private:
void initializeNeighborOffsets();
template <typename Accessor>
size_t hashOfParticle( size_t p_idx, Accessor& ac ) const;
size_t hashPoint(real_t x, real_t y, real_t z) const;
void addParticleToCell( size_t p_idx, Cell* cell );
template <typename Accessor>
void enlarge(Accessor& ac);
Cell* cell_; //!< Linear array of cells representing the three-dimensional hash grid.
size_t xCellCount_; //!< Number of cells allocated in x-axis direction.
size_t yCellCount_; //!< Number of cells allocated in y-axis direction.
size_t zCellCount_; //!< Number of cells allocated in z-axis direction.
size_t xHashMask_; //!< Bit-mask required for the hash calculation in x-axis direction (\a xCellCount_ - 1).
size_t yHashMask_; //!< Bit-mask required for the hash calculation in y-axis direction (\a yCellCount_ - 1).
size_t zHashMask_; //!< Bit-mask required for the hash calculation in z-axis direction (\a zCellCount_ - 1).
size_t xyCellCount_; /*!< \brief Number of cells allocated in x-axis direction multiplied by
the number of cells allocated in y-axis direction. */
public:
size_t xyzCellCount_; //!< Total number of allocated cells.
size_t enlargementThreshold_; /*!< \brief The enlargement threshold - the moment the number
of assigned particles exceeds this threshold, the
size of this hash grid is increased. */
real_t cellSpan_; //!< The grid's cell size (edge length of the underlying cubic grid cells).
real_t inverseCellSpan_; //!< The inverse cell size.
/*!< Required for replacing floating point divisions with multiplications
during the hash computation. */
CellVector occupiedCells_; //!< A grid-global list that keeps track of all particle-occupied cells.
/*!< The list is required in order to avoid iterating through all
grid cells whenever all particles that are stored in the grid
need to be addressed. */
size_t particleCount_; //!< Number of particles assigned to this hash grid.
offset_t stdNeighborOffset_[27]; /*!< \brief Array of offsets to the neighboring cells (valid
for all the inner cells of the hash grid). */
};
//**Type definitions****************************************************************************
//! List for storing all the hash grids that are in use.
/*! This data structure is used to represent the grid hierarchy. All hash grids are stored in
ascending order by the size of their cells. */
using GridList = std::list<shared_ptr<HashGrid>>;
public:
explicit HashGrids() = default;
~HashGrids() = default;
// initializes Hash Grid with given particle
template <typename Accessor>
inline void operator()( size_t p_idx, Accessor& ac );
void clear();
void clearAll();
/**
* Calls the provided functor \p func for all particle pairs.
*
* Additional arguments can be provided. No pairs with twice the same particle are generated.
* No pair is called twice!
* Call syntax for the provided functor
* \code
* func( *this, i, j, std::forward<Args>(args)... );
* \endcode
* \param openmp enables/disables OpenMP parallelization of the kernel call
*/
template <typename Selector, typename Accessor, typename Func, typename... Args>
void forEachParticlePairHalf(const bool openmp,
const Selector& selector,
Accessor& acForLC,
Func&& func,
Args&&... args) const;
private:
inline void addInfiniteParticle(size_t p_idx);
ParticleIdxVector infiniteParticles_;
GridList gridList_; //!< List of all grids that form the hierarchy.
/*!< The grids in this list are sorted in ascending order by the
size of their cells. */
};
// HashGrids::HashGrid member function implementations
HashGrids::HashGrid::HashGrid( real_t cellSpan )
{
// Initialization of all member variables and ...
xCellCount_ = math::uintIsPowerOfTwo( xCellCount ) ? xCellCount : 16;
yCellCount_ = math::uintIsPowerOfTwo( yCellCount ) ? yCellCount : 16;
zCellCount_ = math::uintIsPowerOfTwo( zCellCount ) ? zCellCount : 16;
xHashMask_ = xCellCount_ - 1;
yHashMask_ = yCellCount_ - 1;
zHashMask_ = zCellCount_ - 1;
xyCellCount_ = xCellCount_ * yCellCount_;
xyzCellCount_ = xyCellCount_ * zCellCount_;
enlargementThreshold_ = xyzCellCount_ / minimalGridDensity;
// ... allocation of the linear array that is representing the hash grid.
cell_ = new Cell[ xyzCellCount_ ];
// Initialization of each cell - i.e., initially setting the pointer to the particle container to
// NULL (=> no particles are assigned to this hash grid yet!) and ...
for( Cell* c = cell_; c < cell_ + xyzCellCount_; ++c ) {
c->particles_ = nullptr;
}
// ... setting up the neighborhood relationship (using the offset array).
initializeNeighborOffsets();
cellSpan_ = cellSpan;
inverseCellSpan_ = real_c( 1 ) / cellSpan;
occupiedCells_.reserve( occupiedCellsVectorSize );
particleCount_ = 0;
}
HashGrids::HashGrid::~HashGrid()
{
clear();
for( Cell* c = cell_; c < cell_ + xyzCellCount_; ++c ) {
if( c->neighborOffset_ != stdNeighborOffset_ ) delete[] c->neighborOffset_;
}
delete[] cell_;
}
void HashGrids::HashGrid::clear()
{
for( auto cellIt = occupiedCells_.begin(); cellIt < occupiedCells_.end(); ++cellIt ) {
delete (*cellIt)->particles_;
(*cellIt)->particles_ = nullptr;
}
occupiedCells_.clear();
particleCount_ = 0;
}
//*************************************************************************************************
/*!\brief Sets up the neighborhood relationships for all grid cells.
*
* This function is used to initialize the offset arrays of all grid cells. The offsets are required
* for ensuring fast direct access to all directly adjacent cells for each cell in the hash grid.
*/
void HashGrids::HashGrid::initializeNeighborOffsets()
{
offset_t xc = static_cast<offset_t>( xCellCount_ );
offset_t yc = static_cast<offset_t>( yCellCount_ );
offset_t zc = static_cast<offset_t>( zCellCount_ );
offset_t xyc = static_cast<offset_t>( xyCellCount_ );
offset_t xyzc = static_cast<offset_t>( xyzCellCount_ );
// Initialization of the grid-global offset array that is valid for all inner cells in the hash grid.
unsigned int i = 0;
for( offset_t zz = -xyc; zz <= xyc; zz += xyc ) {
for( offset_t yy = -xc; yy <= xc; yy += xc ) {
for( offset_t xx = -1; xx <= 1; ++xx, ++i ) {
stdNeighborOffset_[i] = xx + yy + zz;
}
}
}
// Allocation and initialization of the offset arrays of all the border cells. All inner cells
// are set to point to the grid-global offset array.
Cell* c = cell_;
for( offset_t z = 0; z < zc; ++z ) {
for( offset_t y = 0; y < yc; ++y ) {
for( offset_t x = 0; x < xc; ++x, ++c ) {
/* border cell */
if( x == 0 || x == (xc - 1) || y == 0 || y == (yc - 1) || z == 0 || z == (zc - 1) ) {
c->neighborOffset_ = new offset_t[27];
i = 0;
for( offset_t zz = -xyc; zz <= xyc; zz += xyc )
{
offset_t zo = zz;
if( z == 0 && zz == -xyc ) {
zo = xyzc - xyc;
}
else if( z == (zc - 1) && zz == xyc ) {
zo = xyc - xyzc;
}
for( offset_t yy = -xc; yy <= xc; yy += xc )
{
offset_t yo = yy;
if( y == 0 && yy == -xc ) {
yo = xyc - xc;
}
else if( y == (yc - 1) && yy == xc ) {
yo = xc - xyc;
}
for( offset_t xx = -1; xx <= 1; ++xx, ++i ) {
offset_t xo = xx;
if( x == 0 && xx == -1 ) {
xo = xc - 1;
}
else if( x == (xc - 1) && xx == 1 ) {
xo = 1 - xc;
}
c->neighborOffset_[i] = xo + yo + zo;
}
}
}
}
/* inner cell */
else {
c->neighborOffset_ = stdNeighborOffset_;
}
}
}
}
}
//*************************************************************************************************
/*!\brief Adds a particle to this hash grid.
*
* This function is called every time a new rigid particle is added to this grid. If adding the particle
* will cause the total number of particles assigned to this grid to exceed the enlargement threshold,
* the size of this hash grid is increased in order to maintain a fixed minimal grid density (=
* ratio of cells to particles). This function may also be called during the update phase.
*/
template <typename Accessor>
void HashGrids::HashGrid::addParticle( size_t p_idx, Accessor& ac )
{
// If adding the particle will cause the total number of particles assigned to this grid to exceed the
// enlargement threshold, the size of this hash grid must be increased.
if( particleCount_ == enlargementThreshold_ ) enlarge(ac);
// Calculate (and store) the hash value (= the particle's cell association) and ...
size_t hash = hashOfParticle( p_idx, ac );
// ... insert the particle into the corresponding cell.
Cell* cell = cell_ + hash;
addParticleToCell( p_idx, cell );
++particleCount_;
}
//*************************************************************************************************
//*************************************************************************************************
/*!\brief Adds a particle to a specific cell in this hash grid.
*/
void HashGrids::HashGrid::addParticleToCell( size_t p_idx, Cell* cell )
{
// If this cell is already occupied by other particles, which means the pointer to the particle
// container holds a valid address and thus the container itself is properly initialized, then
// the particle is simply added to this already existing particle container.
// If, however, the cell is still empty, then the object container, first of all, must be created
// (i.e., allocated) and properly initialized (i.e., sufficient initial storage capacity must be
// reserved). Furthermore, the cell must be inserted into the grid-global vector 'occupiedCells_'
// in which all cells that are currently occupied by particles are recorded.
if( cell->particles_ == nullptr )
{
cell->particles_ = new ParticleIdxVector ;
cell->particles_->reserve( cellVectorSize );
cell->occupiedCellsId_ = occupiedCells_.size();
occupiedCells_.push_back( cell );
}
cell->particles_->push_back( p_idx );
}
//*************************************************************************************************
//*************************************************************************************************
/*!\brief Computes the hash value (= cell association) of a given particle.
*/
template <typename Accessor>
size_t HashGrids::HashGrid::hashOfParticle( size_t p_idx, Accessor& ac ) const
{
auto particlePosition = ac.getPosition(p_idx);
return hashPoint(particlePosition[0], particlePosition[1], particlePosition[2]);
}
//*************************************************************************************************
/*!\brief Computes the hash for a given point.
*