Commit 3111e9d4 authored by Jean-Noël Grad's avatar Jean-Noël Grad Committed by Markus Holzer
Browse files

New hash algorithm for Cell and Vector<int> objects

parent f0a00fb0
......@@ -330,18 +330,28 @@ inline std::istream & operator>>( std::istream & is, Cell & cell )
*
* \param [in] cell The cell to be hashed.
*
* \return a hopefully unique hash.
* \return a hash that is unique for cell indices from 0 to 2 million
* (64bit architectures) or from 0 to 1000 (32bit architectures)
* in all three dimensions.
**********************************************************************************************************************/
inline std::size_t hash_value( const Cell & cell )
{
std::size_t seed = 0;
std::hash<cell_idx_t> hasher;
std::size_t seed;
seed ^= hasher(cell.x()) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(cell.y()) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(cell.z()) + 0x9e3779b9 + (seed<<6) + (seed>>2);
if constexpr( sizeof(std::size_t) >= 8 )
{
seed = (static_cast<std::size_t>(cell.x()) << 42) +
(static_cast<std::size_t>(cell.y()) << 21) +
(static_cast<std::size_t>(cell.z()) << 0);
}
else
{
seed = (static_cast<std::size_t>(cell.x()) << 21) +
(static_cast<std::size_t>(cell.y()) << 10) +
(static_cast<std::size_t>(cell.z()) << 0);
}
return seed;
return seed;
}
......
......@@ -1557,21 +1557,27 @@ struct Vector2LexicographicalyLess
/**
// \brief Function providing a hash value for Vector2.
//
// \tparam T Datatype of the Vector2's elements.
// \tparam T Datatype of the Vector2's elements (only integers are supported).
// \param v The vector the hash is computed for.
// \returns A hash for the entire Vector2.
*/
template< typename T >
template< typename T, typename Enable = std::enable_if_t<std::is_integral_v<T>> >
std::size_t hash_value( const Vector2<T> & v )
{
std::size_t seed = 0;
std::hash<T> hasher;
std::size_t seed;
seed ^= hasher(v[0]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(v[1]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
if constexpr( sizeof(std::size_t) >= 8 )
{
seed = (static_cast<std::size_t>(v[0]) << 42) +
(static_cast<std::size_t>(v[1]) << 21);
}
else
{
seed = (static_cast<std::size_t>(v[0]) << 21) +
(static_cast<std::size_t>(v[1]) << 10);
}
return seed;
}
//**********************************************************************************************************************
......@@ -1687,7 +1693,7 @@ namespace std
{
std::size_t operator()( walberla::Vector2<T> const & v ) const noexcept
{
return walberla::Vector2<T>::hash_value( v );
return walberla::math::hash_value( v );
}
};
} // namespace std
......@@ -1817,22 +1817,29 @@ struct Vector3LexicographicalyLess
/**
// \brief Function providing a hash value for Vector3.
//
// \tparam T Datatype of the Vector3's elements.
// \tparam T Datatype of the Vector3's elements (only integers are supported).
// \param v The vector the hash is computed for.
// \returns A hash for the entire Vector3.
*/
template< typename T >
template< typename T, typename Enable = std::enable_if_t<std::is_integral_v<T>> >
std::size_t hash_value( const Vector3<T> & v )
{
std::size_t seed = 0;
std::hash<T> hasher;
std::size_t seed;
seed ^= hasher(v[0]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(v[1]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(v[2]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
if constexpr( sizeof(std::size_t) >= 8 )
{
seed = (static_cast<std::size_t>(v[0]) << 42) +
(static_cast<std::size_t>(v[1]) << 21) +
(static_cast<std::size_t>(v[2]) << 0);
}
else
{
seed = (static_cast<std::size_t>(v[0]) << 21) +
(static_cast<std::size_t>(v[1]) << 10) +
(static_cast<std::size_t>(v[2]) << 0);
}
return seed;
}
} // namespace math
......
......@@ -21,9 +21,12 @@
//======================================================================================================================
#include "core/cell/Cell.h"
#include "core/math/Vector2.h"
#include "core/math/Vector3.h"
#include "core/debug/TestSubsystem.h"
#include <random>
#include <unordered_set>
using namespace walberla;
......@@ -129,6 +132,63 @@ void testBinaryOperators( const Cell & c0, const Cell & c1 )
WALBERLA_CHECK( ss.eof() );
}
void testHashAlgorithm()
{
auto const hasher = std::hash< walberla::Cell >();
// check hash concatenates individual elements
std::size_t const prefix = hasher(walberla::Cell{15, 6, 0});
cell_idx_t const max_z = (sizeof(std::size_t) >= 8)? 2<<21 : 2<<10;
std::size_t mismatches = 0;
for( cell_idx_t z = 0; z < max_z; z += 5 )
{
auto const cell = walberla::Cell{15, 6, z};
auto const expected_hash = prefix + static_cast<std::size_t>(z);
if( hasher(cell) != expected_hash )
{
mismatches++;
}
}
WALBERLA_CHECK_EQUAL( mismatches, 0 );
// check hash collisions (use a small block size to limit memory footprint)
cell_idx_t const block_size = 128;
cell_idx_t const ghost_layer = 8;
std::unordered_set<std::size_t> keys{};
std::size_t collisions = 0;
for( auto x = -ghost_layer; x < block_size + ghost_layer; ++x )
{
for( auto y = -ghost_layer; y < block_size + ghost_layer; ++y )
{
for( auto z = -ghost_layer; z < block_size + ghost_layer; ++z )
{
auto const cell = walberla::Cell{x, y, z};
auto const hash = hasher(cell);
if (keys.count(hash))
{
collisions++;
}
else
{
keys.emplace(hash);
}
}
}
}
WALBERLA_CHECK_EQUAL( collisions, 0 );
// check hash matches with Vector2 and Vector3
auto const hasher2 = std::hash< walberla::Vector2<int> >();
auto const hasher3 = std::hash< walberla::Vector3<int> >();
auto const cell = walberla::Cell{15, 6, 42};
auto const vec3 = walberla::Vector3<int>{15, 6, 42};
WALBERLA_CHECK_EQUAL( hasher(cell), hasher3(vec3) );
auto const vec2 = walberla::Vector2<int>{15, 6};
auto const vec3b = walberla::Vector3<int>{15, 6, 0};
WALBERLA_CHECK_EQUAL( hasher2(vec2), hasher3(vec3b) );
}
int main( int /*argc*/, char** /*argv*/ ) {
......@@ -153,5 +213,7 @@ int main( int /*argc*/, char** /*argv*/ ) {
testBinaryOperators( Cell(x0, y0, z0), Cell(x1, y1, z1) );
}
testHashAlgorithm();
return 0;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment