Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
waLBerla
waLBerla
Commits
ff87d40d
Commit
ff87d40d
authored
Jun 14, 2021
by
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
Changes
20
Pipelines
2
Show whitespace changes
Inline
Sidebyside
python/mesa_pd.py
View file @
ff87d40d
...
...
@@ 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
))
...
...
python/mesa_pd/data/HashGrids.py
0 → 100644
View file @
ff87d40d
# * coding: utf8 *
from
..utility
import
generate_file
class
HashGrids
:
def
generate
(
self
,
module
):
ctx
=
{
'module'
:
module
}
generate_file
(
module
[
'module_path'
],
'data/HashGrids.templ.h'
,
ctx
)
python/mesa_pd/data/__init__.py
View file @
ff87d40d
...
...
@@ 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'
,
...
...
python/mesa_pd/templates/data/HashGrids.templ.h
0 → 100644
View file @
ff87d40d
//======================================================================================================================
//
// 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 uniquesized 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 axisaligned 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>averagecase 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 wellsuited for largescale 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 xdirection of a newly created hash grid.
*
* This value represents the initial number of cells of a newly created hash grid in xdirection.
* 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 greaterorequal 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 ydirection 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 zdirection 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 gridglobal vector.
*
* This value specifies the initial storage capacity of the gridglobal vector that keeps track
* of all particleoccupied cells. As long as at least one particle is assigned to a certain cell, this
* cell is recorded in a gridglobal list that keeps track of all particleoccupied 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 inbetween 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 (particleoccupied) 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 threedimensional hash grid.
size_t
xCellCount_
;
//!< Number of cells allocated in xaxis direction.
size_t
yCellCount_
;
//!< Number of cells allocated in yaxis direction.
size_t
zCellCount_
;
//!< Number of cells allocated in zaxis direction.
size_t
xHashMask_
;
//!< Bitmask required for the hash calculation in xaxis direction (\a xCellCount_  1).
size_t
yHashMask_
;
//!< Bitmask required for the hash calculation in yaxis direction (\a yCellCount_  1).
size_t
zHashMask_
;
//!< Bitmask required for the hash calculation in zaxis direction (\a zCellCount_  1).
size_t
xyCellCount_
;
/*!< \brief Number of cells allocated in xaxis direction multiplied by
the number of cells allocated in yaxis 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 gridglobal list that keeps track of all particleoccupied 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 gridglobal 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 gridglobal 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 gridglobal 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