Files
mfem/general/hash.hpp
T

534 lines
13 KiB
C++

// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_HASH
#define MFEM_HASH
#include "../config/config.hpp"
#include "array.hpp"
#include "globals.hpp"
namespace mfem
{
/** A concept for items that should be used in HashTable and be accessible by
* hashing two IDs.
*/
struct Hashed2
{
int p1, p2;
int next;
};
/** A concept for items that should be used in HashTable and be accessible by
* hashing four IDs.
*/
struct Hashed4
{
int p1, p2, p3; // NOTE: p4 is neither hashed nor stored
int next;
};
/** HashTable is a container for items that require associative access through
* pairs (or quadruples) of indices:
*
* (p1, p2) -> item
* (p1, p2, p3, p4) -> item
*
* An example of this are edges and faces in a mesh. Each edge is uniquely
* identified by two parent vertices and so can be easily accessed from
* different elements using this class. Similarly for faces.
*
* The order of the p1, p2, ... indices is not relevant as they are sorted
* each time this class is invoked.
*
* There are two main methods this class provides. The Get(...) methods always
* return an item given the two or four indices. If the item didn't previously
* exist, the methods creates a new one. The Find(...) methods, on the other
* hand, just return NULL or -1 if the item doesn't exist.
*
* Each new item is automatically assigned a unique ID - the index of the item
* inside the BlockArray. The IDs may (but need not) be used as p1, p2, ... of
* other items.
*
* The item type (T) needs to follow either the Hashed2 or the Hashed4
* concept. It is easiest to just inherit from these structs.
*
* All items in the container can also be accessed sequentially using the
* provided iterator.
*/
template<typename T>
class HashTable : public BlockArray<T>
{
protected:
typedef BlockArray<T> Base;
public:
HashTable(int block_size = 16*1024, int init_hash_size = 32*1024);
HashTable(const HashTable& other); // deep copy
~HashTable();
/// Get item whose parents are 'p1', 'p2'... Create it if it doesn't exist.
T* Get(int p1, int p2);
T* Get(int p1, int p2, int p3, int p4 = -1 /* p4 optional */);
/// Get id of item whose parents are p1, p2... Create it if it doesn't exist.
int GetId(int p1, int p2);
int GetId(int p1, int p2, int p3, int p4 = -1);
/// Find item whose parents are p1, p2... Return NULL if it doesn't exist.
T* Find(int p1, int p2);
T* Find(int p1, int p2, int p3, int p4 = -1);
const T* Find(int p1, int p2) const;
const T* Find(int p1, int p2, int p3, int p4 = -1) const;
/// Find id of item whose parents are p1, p2... Return -1 if it doesn't exist.
int FindId(int p1, int p2) const;
int FindId(int p1, int p2, int p3, int p4 = -1) const;
/// Return the number of elements currently stored in the HashTable.
int Size() const { return Base::Size() - unused.Size(); }
/// Return the total number of ids (used and unused) in the HashTable.
int NumIds() const { return Base::Size(); }
/// Return the number of free/unused ids in the HashTable.
int NumFreeIds() const { return unused.Size(); }
/// Return true if item 'id' exists in (is used by) the container.
/** It is assumed that 0 <= id < NumIds(). */
bool IdExists(int id) const { return (Base::At(id).next != -2); }
/// Remove an item from the hash table.
/** Its id will be reused by newly added items. */
void Delete(int id);
/// Remove all items.
void DeleteAll();
/// Make an item hashed under different parent IDs.
void Reparent(int id, int new_p1, int new_p2);
void Reparent(int id, int new_p1, int new_p2, int new_p3, int new_p4 = -1);
/// Return total size of allocated memory (tables plus items), in bytes.
long MemoryUsage() const;
/// Write details of the memory usage to the mfem output stream.
void PrintMemoryDetail() const;
class iterator : public Base::iterator
{
protected:
friend class HashTable;
typedef typename Base::iterator base;
iterator() { }
iterator(const base &it) : base(it)
{
while (base::good() && (*this)->next == -2) { base::next(); }
}
public:
iterator &operator++()
{
while (base::next(), base::good() && (*this)->next == -2) { }
return *this;
}
};
class const_iterator : public Base::const_iterator
{
protected:
friend class HashTable;
typedef typename Base::const_iterator base;
const_iterator() { }
const_iterator(const base &it) : base(it)
{
while (base::good() && (*this)->next == -2) { base::next(); }
}
public:
const_iterator &operator++()
{
while (base::next(), base::good() && (*this)->next == -2) { }
return *this;
}
};
iterator begin() { return iterator(Base::begin()); }
iterator end() { return iterator(); }
const_iterator cbegin() const { return const_iterator(Base::cbegin()); }
const_iterator cend() const { return const_iterator(); }
protected:
int* table;
int mask;
Array<int> unused;
// hash functions (NOTE: the constants are arbitrary)
inline int Hash(int p1, int p2) const
{ return (984120265*p1 + 125965121*p2) & mask; }
inline int Hash(int p1, int p2, int p3) const
{ return (984120265*p1 + 125965121*p2 + 495698413*p3) & mask; }
// Delete() and Reparent() use one of these:
inline int Hash(const Hashed2& item) const
{ return Hash(item.p1, item.p2); }
inline int Hash(const Hashed4& item) const
{ return Hash(item.p1, item.p2, item.p3); }
int SearchList(int id, int p1, int p2) const;
int SearchList(int id, int p1, int p2, int p3) const;
inline void Insert(int idx, int id, T &item);
void Unlink(int idx, int id);
/// Check table load factor and resize if necessary
inline void CheckRehash();
void DoRehash();
};
// implementation
template<typename T>
HashTable<T>::HashTable(int block_size, int init_hash_size)
: Base(block_size)
{
mask = init_hash_size-1;
MFEM_VERIFY(!(init_hash_size & mask), "init_size must be a power of two.");
table = new int[init_hash_size];
for (int i = 0; i < init_hash_size; i++) { table[i] = -1; }
}
template<typename T>
HashTable<T>::HashTable(const HashTable& other)
: Base(other), mask(other.mask)
{
int size = mask+1;
table = new int[size];
memcpy(table, other.table, size*sizeof(int));
other.unused.Copy(unused);
}
template<typename T>
HashTable<T>::~HashTable()
{
delete [] table;
}
namespace internal
{
inline void sort3(int &a, int &b, int &c)
{
if (a > b) { std::swap(a, b); }
if (a > c) { std::swap(a, c); }
if (b > c) { std::swap(b, c); }
}
inline void sort4(int &a, int &b, int &c, int &d)
{
if (a > b) { std::swap(a, b); }
if (a > c) { std::swap(a, c); }
if (a > d) { std::swap(a, d); }
sort3(b, c, d);
}
inline void sort4_ext(int &a, int &b, int &c, int &d)
{
if (d < 0) // support optional last index
{
sort3(a, b, c);
}
else
{
sort4(a, b, c, d);
}
}
} // internal
template<typename T>
inline T* HashTable<T>::Get(int p1, int p2)
{
return &(Base::At(GetId(p1, p2)));
}
template<typename T>
inline T* HashTable<T>::Get(int p1, int p2, int p3, int p4)
{
return &(Base::At(GetId(p1, p2, p3, p4)));
}
template<typename T>
int HashTable<T>::GetId(int p1, int p2)
{
// search for the item in the hashtable
if (p1 > p2) { std::swap(p1, p2); }
int idx = Hash(p1, p2);
int id = SearchList(table[idx], p1, p2);
if (id >= 0) { return id; }
// not found - use an unused item or create a new one
int new_id;
if (unused.Size())
{
new_id = unused.Last();
unused.DeleteLast();
}
else
{
new_id = Base::Append();
}
T& item = Base::At(new_id);
item.p1 = p1;
item.p2 = p2;
// insert into hashtable
Insert(idx, new_id, item);
CheckRehash();
return new_id;
}
template<typename T>
int HashTable<T>::GetId(int p1, int p2, int p3, int p4)
{
// search for the item in the hashtable
internal::sort4_ext(p1, p2, p3, p4);
int idx = Hash(p1, p2, p3);
int id = SearchList(table[idx], p1, p2, p3);
if (id >= 0) { return id; }
// not found - use an unused item or create a new one
int new_id;
if (unused.Size())
{
new_id = unused.Last();
unused.DeleteLast();
}
else
{
new_id = Base::Append();
}
T& item = Base::At(new_id);
item.p1 = p1;
item.p2 = p2;
item.p3 = p3;
// insert into hashtable
Insert(idx, new_id, item);
CheckRehash();
return new_id;
}
template<typename T>
inline T* HashTable<T>::Find(int p1, int p2)
{
int id = FindId(p1, p2);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
inline T* HashTable<T>::Find(int p1, int p2, int p3, int p4)
{
int id = FindId(p1, p2, p3, p4);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
inline const T* HashTable<T>::Find(int p1, int p2) const
{
int id = FindId(p1, p2);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
inline const T* HashTable<T>::Find(int p1, int p2, int p3, int p4) const
{
int id = FindId(p1, p2, p3, p4);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
int HashTable<T>::FindId(int p1, int p2) const
{
if (p1 > p2) { std::swap(p1, p2); }
return SearchList(table[Hash(p1, p2)], p1, p2);
}
template<typename T>
int HashTable<T>::FindId(int p1, int p2, int p3, int p4) const
{
internal::sort4_ext(p1, p2, p3, p4);
return SearchList(table[Hash(p1, p2, p3)], p1, p2, p3);
}
template<typename T>
int HashTable<T>::SearchList(int id, int p1, int p2) const
{
while (id >= 0)
{
const T& item = Base::At(id);
if (item.p1 == p1 && item.p2 == p2) { return id; }
id = item.next;
}
return -1;
}
template<typename T>
int HashTable<T>::SearchList(int id, int p1, int p2, int p3) const
{
while (id >= 0)
{
const T& item = Base::At(id);
if (item.p1 == p1 && item.p2 == p2 && item.p3 == p3) { return id; }
id = item.next;
}
return -1;
}
template<typename T>
inline void HashTable<T>::CheckRehash()
{
const int fill_factor = 2;
// is the table overfull?
if (Base::Size() > (mask+1) * fill_factor)
{
DoRehash();
}
}
template<typename T>
void HashTable<T>::DoRehash()
{
delete [] table;
// double the table size
int new_table_size = 2*(mask+1);
table = new int[new_table_size];
for (int i = 0; i < new_table_size; i++) { table[i] = -1; }
mask = new_table_size-1;
#if defined(MFEM_DEBUG) && !defined(MFEM_USE_MPI)
mfem::out << _MFEM_FUNC_NAME << ": rehashing to size " << new_table_size
<< std::endl;
#endif
// reinsert all items
for (iterator it = begin(); it != end(); ++it)
{
Insert(Hash(*it), it.index(), *it);
}
}
template<typename T>
inline void HashTable<T>::Insert(int idx, int id, T &item)
{
// add item at the beginning of the linked list
item.next = table[idx];
table[idx] = id;
}
template<typename T>
void HashTable<T>::Unlink(int idx, int id)
{
// remove item from the linked list
int* p_id = table + idx;
while (*p_id >= 0)
{
T& item = Base::At(*p_id);
if (*p_id == id)
{
*p_id = item.next;
return;
}
p_id = &(item.next);
}
MFEM_ABORT("HashTable<>::Unlink: item not found!");
}
template<typename T>
void HashTable<T>::Delete(int id)
{
T& item = Base::At(id);
Unlink(Hash(item), id);
item.next = -2; // mark item as unused
unused.Append(id); // add its id to the unused ids
}
template<typename T>
void HashTable<T>::DeleteAll()
{
Base::DeleteAll();
for (int i = 0; i <= mask; i++) { table[i] = -1; }
unused.DeleteAll();
}
template<typename T>
void HashTable<T>::Reparent(int id, int new_p1, int new_p2)
{
T& item = Base::At(id);
Unlink(Hash(item), id);
if (new_p1 > new_p2) { std::swap(new_p1, new_p2); }
item.p1 = new_p1;
item.p2 = new_p2;
// reinsert under new parent IDs
int new_idx = Hash(new_p1, new_p2);
Insert(new_idx, id, item);
}
template<typename T>
void HashTable<T>::Reparent(int id,
int new_p1, int new_p2, int new_p3, int new_p4)
{
T& item = Base::At(id);
Unlink(Hash(item), id);
internal::sort4_ext(new_p1, new_p2, new_p3, new_p4);
item.p1 = new_p1;
item.p2 = new_p2;
item.p3 = new_p3;
// reinsert under new parent IDs
int new_idx = Hash(new_p1, new_p2, new_p3);
Insert(new_idx, id, item);
}
template<typename T>
long HashTable<T>::MemoryUsage() const
{
return (mask+1) * sizeof(int) + Base::MemoryUsage() + unused.MemoryUsage();
}
template<typename T>
void HashTable<T>::PrintMemoryDetail() const
{
mfem::out << Base::MemoryUsage() << " + " << (mask+1) * sizeof(int)
<< " + " << unused.MemoryUsage();
}
} // namespace mfem
#endif