Introduce SimulatorBuilder
[gromacs.git] / src / gromacs / domdec / hashedmap.h
blobbb0ccedab97bf1cff86f49d895c90b595bff93fe
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2018, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
35 /*! \libinternal \file
36 * \brief
37 * Defines structures and functions for mapping from keys to entries
38 * indices using a hash table.
39 * The functions are performance critical and should be inlined.
41 * \inlibraryapi
42 * \ingroup module_domdec
44 * \author Berk Hess <hess@kth.se>
47 #ifndef GMX_DOMDEC_HASHEDMAP_H
48 #define GMX_DOMDEC_HASHEDMAP_H
50 #include <climits>
52 #include <algorithm>
53 #include <vector>
55 #include "gromacs/compat/utility.h"
56 #include "gromacs/utility/basedefinitions.h"
57 #include "gromacs/utility/exceptions.h"
59 namespace gmx
62 /*! \libinternal \brief Unordered key to value mapping
64 * Efficiently manages mapping from integer keys to values.
65 * Note that this basically implements a subset of the functionality of
66 * std::unordered_map, but is an order of magnitude faster.
68 template <class T>
69 class HashedMap
71 private:
72 /*! \libinternal \brief Structure for the key/value hash table */
73 struct hashEntry
75 int key = -1; /**< The key */
76 T value; /**< The value(s) */
77 int next = -1; /**< Index in the list of the next element with the same hash, -1 if none */
80 /*! \brief The table size is set to at least this factor time the nr of keys */
81 static constexpr float c_relTableSizeSetMin = 1.5;
82 /*! \brief Threshold for increasing the table size */
83 static constexpr float c_relTableSizeThresholdMin = 1.3;
84 /*! \brief Threshold for decreasing the table size */
85 static constexpr float c_relTableSizeThresholdMax = 3.5;
87 /*! \brief Resizes the table
89 * \param[in] numElementsEstimate An estimate of the number of elements that will be stored
91 void resize(int numElementsEstimate)
93 GMX_RELEASE_ASSERT(numElements_ == 0, "Table needs to be empty for resize");
95 /* The fraction of table entries with 0 size lists is e^-f.
96 * The fraction of table entries with >=1 size lists is 1 - e^-f
97 * where f is: the #elements / tableSize
98 * The fraction of elements not in the direct list is: 1 - (1 - e^-f)/f.
99 * Thus the optimal table size is roughly double #elements.
101 /* Make the hash table a power of 2 and at least 1.5 * #elements */
102 int tableSize = 64;
103 while (tableSize <= INT_MAX/2 &&
104 numElementsEstimate*c_relTableSizeSetMin > tableSize)
106 tableSize *= 2;
108 table_.resize(tableSize);
110 /* Table size is a power of 2, so a binary mask gives the hash */
111 bitMask_ = tableSize - 1;
112 startIndexForSpaceForListEntry_ = tableSize;
115 public:
116 /*! \brief Constructor
118 * \param[in] numElementsEstimate An estimate of the number of elements that will be stored, used for optimizing initial performance
120 * Note that the estimate of the number of elements is only relevant
121 * for the performance up until the first call to clear(), after which
122 * table size is optimized based on the actual number of elements.
124 HashedMap(int numElementsEstimate)
126 resize(numElementsEstimate);
129 /*! \brief Returns the number of elements */
130 int size() const
132 return numElements_;
135 /*! \brief Returns the number of buckets, i.e. the number of possible hashes */
136 int bucket_count() const
138 return bitMask_ + 1;
141 private:
142 /*! \brief Inserts or assigns a key and value
144 * \tparam allowAssign Sets whether assignment of a key that is present is allowed
145 * \param[in] key The key for the entry
146 * \param[in] value The value for the entry
147 * \throws InvalidInputError from a debug build when attempting to insert a duplicate key with \p allowAssign=true
149 // cppcheck-suppress unusedPrivateFunction
150 template<bool allowAssign> void insert_assign(int key,
151 const T &value)
153 size_t ind = (key & bitMask_);
155 if (table_[ind].key >= 0)
157 /* Loop over the entries for this hash.
158 * If we find the matching key, return the value.
160 int ind_prev = ind;
161 if (table_[ind_prev].key == key)
163 if (allowAssign)
165 table_[ind].value = value;
166 return;
168 else
170 // Note: This is performance critical, so we only throw in debug mode
171 #ifndef NDEBUG
172 GMX_THROW(InvalidInputError("Attempt to insert duplicate key"));
173 #endif
176 while (table_[ind_prev].next >= 0)
178 ind_prev = table_[ind_prev].next;
179 if (table_[ind_prev].key == key)
181 if (allowAssign)
183 table_[ind_prev].value = value;
184 return;
186 else
188 #ifndef NDEBUG
189 GMX_THROW(InvalidInputError("Attempt to insert duplicate key"));
190 #endif
194 /* Search for space in table_ */
195 ind = startIndexForSpaceForListEntry_;
196 while (ind < table_.size() && table_[ind].key >= 0)
198 ind++;
200 /* If we are at the end of the list we need to increase the size */
201 if (ind == table_.size())
203 table_.resize(table_.size() + 1);
205 table_[ind_prev].next = ind;
207 startIndexForSpaceForListEntry_ = ind + 1;
210 table_[ind].key = key;
211 table_[ind].value = value;
213 numElements_ += 1;
216 public:
217 /*! \brief Inserts entry, key should not already be present
219 * \param[in] key The key for the entry
220 * \param[in] value The value for the entry
221 * \throws InvalidInputError from a debug build when attempting to inser */
222 void insert(int key,
223 const T &value)
225 insert_assign<false>(key, value);
228 /*! \brief Inserts an entry when the key is not present, otherwise sets the value
230 * \param[in] key The key for the entry
231 * \param[in] value The value for the entry
233 void insert_or_assign(int key,
234 const T &value)
236 insert_assign<true>(key, value);
239 /*! \brief Delete the entry for key \p key, when present
241 * \param[in] key The key
243 void erase(int key)
245 int ind_prev = -1;
246 int ind = (key & bitMask_);
249 if (table_[ind].key == key)
251 if (ind_prev >= 0)
253 table_[ind_prev].next = table_[ind].next;
255 /* This index is a linked entry, so we free an entry.
256 * Check if we are creating the first empty space.
258 if (ind < startIndexForSpaceForListEntry_)
260 startIndexForSpaceForListEntry_ = ind;
263 table_[ind].key = -1;
264 table_[ind].next = -1;
266 numElements_ -= 1;
268 return;
270 ind_prev = ind;
271 ind = table_[ind].next;
273 while (ind >= 0);
276 /*! \brief Returns a pointer to the value for the given key or nullptr when not present
278 * \param[in] key The key
279 * \return a pointer to value for the given key or nullptr when not present
281 T *find(int key) { return const_cast<T*>(gmx::compat::as_const(*this).find(key)); }
283 /*! \brief Returns a pointer to the value for the given key or nullptr when not present
285 * \param[in] key The key
286 * \return a pointer to value for the given key or nullptr when not present
288 const T *find(int key) const
290 int ind = (key & bitMask_);
293 if (table_[ind].key == key)
295 return &table_[ind].value;
297 ind = table_[ind].next;
299 while (ind >= 0);
301 return nullptr;
304 /*! \brief Clear all the entries in the list
306 * Also optimizes the size of the table based on the current
307 * number of elements stored.
309 void clear()
311 const int oldNumElements = numElements_;
313 for (hashEntry &entry : table_)
315 entry.key = -1;
316 entry.next = -1;
318 startIndexForSpaceForListEntry_ = bucket_count();
319 numElements_ = 0;
321 /* Resize the hash table when the occupation is far from optimal.
322 * Do not resize with 0 elements to avoid minimal size when clear()
323 * is called twice in a row.
325 if (oldNumElements > 0 && (oldNumElements*c_relTableSizeThresholdMax < bucket_count() ||
326 oldNumElements*c_relTableSizeThresholdMin > bucket_count()))
328 resize(oldNumElements);
332 private:
333 /*! \brief The hash table list */
334 std::vector<hashEntry> table_;
335 /*! \brief The bit mask for computing the hash of a key */
336 int bitMask_ = 0;
337 /*! \brief Index in table_ at which to start looking for empty space for a new linked list entry */
338 int startIndexForSpaceForListEntry_ = 0;
339 /*! \brief The number of elements currently stored in the table */
340 int numElements_ = 0;
343 } // namespace gmx
345 #endif