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__':
cs.add_property("diag_n_inv", "real_t", 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.SparseLinkedCells())
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 @@
from .ContactHistory import ContactHistory
from .ContactStorage import ContactStorage
from .HashGrids import HashGrids
from .LinkedCells import LinkedCells
from .ParticleStorage import ParticleStorage
from .ShapeStorage import ShapeStorage
......@@ -9,6 +10,7 @@ from .SparseLinkedCells import SparseLinkedCells
__all__ = ['ContactHistory',
'ContactStorage',
'HashGrids',
'LinkedCells',
'ParticleStorage',
'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.
*
* The hash calculation uses modulo operations in order to spatially map entire blocks of connected
* cells to the origin of the coordinate system. This block of cells at the origin of the coordinate
* system that is filled with all the particles of the simulation is referred to as the hash grid. The
* key feature, and ultimately the advantage, of hash grids is the fact that two adjacent cells that
* are located anywhere in the simulation space are mapped to two cells that are still adjacent in
* the hashed storage structure.
*
* Note that the modulo calculations are replaced with fast bitwise AND operations - hence, the