(git:1f285aa)
qs_nl_hash_table_types.F
Go to the documentation of this file.
1 !--------------------------------------------------------------------------------------------------!
2 ! CP2K: A general program to perform molecular dynamics simulations !
3 ! Copyright 2000-2024 CP2K developers group <https://cp2k.org> !
4 ! !
5 ! SPDX-License-Identifier: GPL-2.0-or-later !
6 !--------------------------------------------------------------------------------------------------!
7 
8 ! **************************************************************************************************
9 !> \brief A simple hash table of integer keys, using hash function:
10 !> H(k) = (k*p) mod n + 1
11 !> where:
12 !> k = key
13 !> p = a prime number >= n
14 !> n = size of the hash table
15 !> And collision resolvation is done by open addressing with linear
16 !> probing.
17 !>
18 !> The table consists of an array of (key,val) pairs, and
19 !> there are no intermediate buckets. For every new entry (k,v):
20 !> We first look up slot H(k), and if it already contains an entry,
21 !> then move to the next empty slot using a predefined linear probing
22 !> sequence (e.g. iterate from slots H(k) to n, and then 1 to H(k)-1).
23 !> When we look up, we use the same probing sequence.
24 !>
25 !> Derived from qs_fb_hash_table_types.F (Mark Tucker, Jun 2016)
26 ! **************************************************************************************************
28 
29  USE kinds, ONLY: int_8
31  USE qs_neighbor_list_types, ONLY: neighbor_list_task_type
32 #include "./base/base_uses.f90"
33 
34  IMPLICIT NONE
35 
36  PRIVATE
37 
38 ! public types
39  PUBLIC :: nl_hash_table_obj
40 
41 ! public methods
42  PUBLIC :: nl_hash_table_create, & !create new table
43  nl_hash_table_release, & !destroy existing table
44  nl_hash_table_add, & !add a new entry to the table
45  nl_hash_table_get_from_index, & !return the value from the specified index of the table
48 
49  CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_nl_hash_table_types'
50 
51 ! key value indicating an empty slot
52  INTEGER(KIND=int_8), PARAMETER, PRIVATE :: EMPTY_KEY = -1_int_8
53 ! Parameters related to automatic resizing of the hash_table:
54 ! Resize by EXPAND_FACTOR if total no. slots / no. of filled slots < ENLARGE_RATIO
55  INTEGER, PARAMETER, PRIVATE :: ENLARGE_RATIO = 1
56  INTEGER, PARAMETER, PRIVATE :: REDUCE_RATIO = 3
57  INTEGER, PARAMETER, PRIVATE :: EXPAND_FACTOR = 2
58  INTEGER, PARAMETER, PRIVATE :: SHRINK_FACTOR = 2
59 
60 ! **************************************************************************************************
61 !> \brief hash table entry data type
62 !> \param key : key of the entry
63 !> \param val : value of the entry
64 ! **************************************************************************************************
65  TYPE nl_hash_table_element
66  INTEGER(KIND=int_8) :: key
67  TYPE(neighbor_list_task_type), POINTER :: val
68  END TYPE nl_hash_table_element
69 
70 ! **************************************************************************************************
71 !> \brief data defining a hash table using open addressing for collision
72 !> resolvation. Uses simple entry structure to be memory efficient
73 !> as well as small overhead
74 !> \param table : hash table data area
75 !> \param nelements : number of non-empty slots in table
76 !> \param nmax : max number of slots in table
77 !> \param prime : prime number used in the hash function
78 ! **************************************************************************************************
79  TYPE nl_hash_table_data
80  TYPE(nl_hash_table_element), DIMENSION(:), POINTER :: table => null()
81  INTEGER :: nelements
82  INTEGER :: nmax
83  INTEGER :: prime
84  END TYPE nl_hash_table_data
85 
86 ! **************************************************************************************************
87 !> \brief the object container which allows for the creation of an array
88 !> of pointers to nl_hash_table objects
89 !> \param obj : pointer to the nl_hash_table object
90 ! **************************************************************************************************
91  TYPE nl_hash_table_obj
92  TYPE(nl_hash_table_data), POINTER, PRIVATE :: obj => null()
93  END TYPE nl_hash_table_obj
94 
95 CONTAINS
96 
97 ! **************************************************************************************************
98 !> \brief Add element to a hash table, auto resize if necessary
99 !> \param hash_table : the nl_hash_table object
100 !> \param key : key of the element
101 !> \param val : value of the element
102 ! **************************************************************************************************
103  RECURSIVE SUBROUTINE nl_hash_table_add(hash_table, key, val)
104  TYPE(nl_hash_table_obj), INTENT(INOUT) :: hash_table
105  INTEGER(KIND=int_8), INTENT(IN) :: key
106  TYPE(neighbor_list_task_type), INTENT(IN), POINTER :: val
107 
108  INTEGER :: islot
109  LOGICAL :: check_ok
110 
111  check_ok = nl_hash_table_has_data(hash_table)
112  cpassert(check_ok)
113 
114  ! check hash table size, if too small rehash in a larger table
115  IF (hash_table%obj%nelements*enlarge_ratio .GE. hash_table%obj%nmax) THEN
116  CALL nl_hash_table_rehash(hash_table=hash_table, nmax=hash_table%obj%nmax*expand_factor)
117  END IF
118 
119  ! find the right slot for the given key
120  islot = nl_hash_table_linear_probe(hash_table, key)
121  cpassert(islot > 0)
122 
123  ! add a new task to the list of tasks with that key
124  IF (hash_table%obj%table(islot)%key == empty_key) THEN
125  hash_table%obj%nelements = hash_table%obj%nelements + 1
126  hash_table%obj%table(islot)%key = key
127  END IF
128 
129  ! If a task exists, we make our new task point to that i.e. adding it to the beginning of the list
130  IF (ASSOCIATED(hash_table%obj%table(islot)%val)) THEN
131  val%next => hash_table%obj%table(islot)%val
132  END IF
133 
134  ! store the (maybe new) first item in the list in the hash table
135  hash_table%obj%table(islot)%val => val
136  END SUBROUTINE nl_hash_table_add
137 
138 ! **************************************************************************************************
139 !> \brief Creates and initialises an empty nl_hash_table object
140 !> \param hash_table : the nl_hash_table object, its content must be NULL and cannot be UNDEFINED
141 !> \param nmax : total size of the table, optional. If absent default size is 1.
142 ! **************************************************************************************************
143  SUBROUTINE nl_hash_table_create(hash_table, nmax)
144  TYPE(nl_hash_table_obj), INTENT(INOUT) :: hash_table
145  INTEGER, INTENT(IN), OPTIONAL :: nmax
146 
147  INTEGER :: my_nmax
148  LOGICAL :: check_ok
149 
150  check_ok = .NOT. nl_hash_table_has_data(hash_table)
151  cpassert(check_ok)
152  ALLOCATE (hash_table%obj)
153  NULLIFY (hash_table%obj%table)
154  hash_table%obj%nmax = 0
155  hash_table%obj%nelements = 0
156  hash_table%obj%prime = 2
157  my_nmax = 1
158  IF (PRESENT(nmax)) my_nmax = nmax
159  CALL nl_hash_table_init(hash_table=hash_table, nmax=my_nmax)
160 
161  END SUBROUTINE nl_hash_table_create
162 
163 ! **************************************************************************************************
164 !> \brief Retrieve value from a hash table given a specified index
165 !> \param hash_table : the nl_hash_table object
166 !> \param idx : the index to retrieve the data for
167 !> \param val : output value, might be unassociated if there is no data with that index
168 ! **************************************************************************************************
169  SUBROUTINE nl_hash_table_get_from_index(hash_table, idx, val)
170  TYPE(nl_hash_table_obj), INTENT(IN) :: hash_table
171  INTEGER, INTENT(IN) :: idx
172  TYPE(neighbor_list_task_type), INTENT(OUT), &
173  POINTER :: val
174 
175  LOGICAL :: check_ok
176 
177  cpassert((idx .GT. 0) .AND. (idx .LE. hash_table%obj%nmax))
178 
179  check_ok = nl_hash_table_has_data(hash_table)
180  cpassert(check_ok)
181 
182  val => hash_table%obj%table(idx)%val
183 
184  END SUBROUTINE nl_hash_table_get_from_index
185 
186 ! **************************************************************************************************
187 !> \brief check if the object has data associated to it
188 !> \param hash_table : the nl_hash_table object in question
189 !> \return : true if hash_table%obj is associated, false otherwise
190 ! **************************************************************************************************
191  PURE FUNCTION nl_hash_table_has_data(hash_table) RESULT(res)
192  TYPE(nl_hash_table_obj), INTENT(IN) :: hash_table
193  LOGICAL :: res
194 
195  res = ASSOCIATED(hash_table%obj)
196  END FUNCTION nl_hash_table_has_data
197 
198 ! **************************************************************************************************
199 !> \brief Initialises a nl_hash_table object
200 !> \param hash_table : the nl_hash_table object, its content must be NULL and cannot be UNDEFINED
201 !> \param nmax : new size of the table, optional. If absent use the old size
202 ! **************************************************************************************************
203  SUBROUTINE nl_hash_table_init(hash_table, nmax)
204  TYPE(nl_hash_table_obj), INTENT(INOUT) :: hash_table
205  INTEGER, INTENT(IN), OPTIONAL :: nmax
206 
207  INTEGER :: ii, my_nmax, two_to_power
208  LOGICAL :: check_ok
209 
210  check_ok = nl_hash_table_has_data(hash_table)
211  cpassert(check_ok)
212  my_nmax = hash_table%obj%nmax
213  IF (PRESENT(nmax)) my_nmax = nmax
214 
215  ! table length should always be power of 2. Find the least
216  ! power that is greater or equal to my_nmax
217  two_to_power = 1 ! = 2**0
218  DO WHILE (two_to_power .LT. my_nmax)
219  two_to_power = 2*two_to_power
220  END DO
221  my_nmax = two_to_power
222 
223  IF (ASSOCIATED(hash_table%obj%table)) THEN
224  IF (SIZE(hash_table%obj%table) .NE. my_nmax) THEN
225  DEALLOCATE (hash_table%obj%table)
226  ALLOCATE (hash_table%obj%table(my_nmax))
227  END IF
228  ELSE
229  ALLOCATE (hash_table%obj%table(my_nmax))
230  END IF
231  hash_table%obj%nmax = my_nmax
232  hash_table%obj%prime = hash_table_matching_prime(my_nmax)
233 
234  ! initiate element to be "empty"
235  DO ii = 1, hash_table%obj%nmax
236  hash_table%obj%table(ii)%key = empty_key
237  NULLIFY (hash_table%obj%table(ii)%val)
238  END DO
239  hash_table%obj%nelements = 0
240  END SUBROUTINE nl_hash_table_init
241 
242 ! **************************************************************************************************
243 !> \brief Initialises a nl_hash_table object
244 !> \param hash_table : the nl_hash_table object, its content must be NULL and cannot be UNDEFINED
245 !> \param key ...
246 !> \param is_null ...
247 ! **************************************************************************************************
248  SUBROUTINE nl_hash_table_is_null(hash_table, key, is_null)
249  TYPE(nl_hash_table_obj), INTENT(IN) :: hash_table
250  INTEGER, INTENT(IN) :: key
251  LOGICAL, INTENT(OUT) :: is_null
252 
253  LOGICAL :: check_ok
254 
255  check_ok = nl_hash_table_has_data(hash_table)
256  cpassert(check_ok)
257  check_ok = (key .LE. hash_table%obj%nmax)
258  cpassert(check_ok)
259 
260  is_null = .false.
261  IF (empty_key == hash_table%obj%table(key)%key) THEN !.OR.
262  !NULLIFY(hash_table%obj%table(key)%val)
263  is_null = .true.
264  END IF
265  END SUBROUTINE nl_hash_table_is_null
266 
267 ! **************************************************************************************************
268 !> \brief Rehash table. If nmax is present, then also change the table size
269 !> to MAX(nmax, number_of_non_empty_elements).
270 !> \param hash_table : the nl_hash_table object
271 !> \param nmax [OPTIONAL] : maximum size of the rehashed table
272 ! **************************************************************************************************
273  RECURSIVE SUBROUTINE nl_hash_table_rehash(hash_table, nmax)
274  TYPE(nl_hash_table_obj), INTENT(INOUT) :: hash_table
275  INTEGER, INTENT(IN), OPTIONAL :: nmax
276 
277  INTEGER :: ii, my_nmax
278  TYPE(nl_hash_table_element), ALLOCATABLE, &
279  DIMENSION(:) :: tmp_table
280 
281  IF (.NOT. nl_hash_table_has_data(hash_table)) THEN
282  CALL nl_hash_table_create(hash_table, nmax)
283  RETURN
284  END IF
285  IF (PRESENT(nmax)) THEN
286  my_nmax = max(nmax, hash_table%obj%nelements)
287  ELSE
288  my_nmax = hash_table%obj%nmax
289  END IF
290  ALLOCATE (tmp_table(hash_table%obj%nmax))
291  tmp_table(:) = hash_table%obj%table(:)
292  CALL nl_hash_table_release(hash_table)
293  CALL nl_hash_table_create(hash_table=hash_table, nmax=my_nmax)
294  DO ii = 1, SIZE(tmp_table)
295  IF (tmp_table(ii)%key .NE. empty_key) THEN
296  CALL nl_hash_table_add(hash_table=hash_table, &
297  key=tmp_table(ii)%key, &
298  val=tmp_table(ii)%val)
299  END IF
300  END DO
301  DEALLOCATE (tmp_table)
302  END SUBROUTINE nl_hash_table_rehash
303 
304 ! **************************************************************************************************
305 !> \brief releases the hash table. Note that deallocating tasks stored in the table
306 !> is the responsibility of the caller
307 !> \param hash_table : the nl_hash_table object in question
308 ! **************************************************************************************************
309  SUBROUTINE nl_hash_table_release(hash_table)
310  TYPE(nl_hash_table_obj), INTENT(INOUT) :: hash_table
311 
312  IF (ASSOCIATED(hash_table%obj)) THEN
313  IF (ASSOCIATED(hash_table%obj%table)) THEN
314  DEALLOCATE (hash_table%obj%table)
315  END IF
316  DEALLOCATE (hash_table%obj)
317  ELSE
318  NULLIFY (hash_table%obj)
319  END IF
320  END SUBROUTINE nl_hash_table_release
321 
322 ! **************************************************************************************************
323 !> \brief outputs the current information about the table
324 !> \param hash_table : the nl_hash_table object in question
325 !> \param nelements : number of non-empty slots in the table
326 !> \param nmax : maximum number of slots in the table
327 !> \param prime : the prime used in the hash function
328 ! **************************************************************************************************
329  SUBROUTINE nl_hash_table_status(hash_table, nelements, nmax, prime)
330  TYPE(nl_hash_table_obj), INTENT(INOUT) :: hash_table
331  INTEGER, INTENT(OUT), OPTIONAL :: nelements, nmax, prime
332 
333  LOGICAL :: check_ok
334 
335  check_ok = nl_hash_table_has_data(hash_table)
336  cpassert(check_ok)
337  IF (PRESENT(nelements)) nelements = hash_table%obj%nelements
338  IF (PRESENT(nmax)) nmax = hash_table%obj%nmax
339  IF (PRESENT(prime)) prime = hash_table%obj%prime
340  END SUBROUTINE nl_hash_table_status
341 
342 ! **************************************************************************************************
343 !> \brief Linear probing algorithm for the hash table
344 !> \param hash_table : the nl_hash_table object
345 !> \param key : key to locate
346 !> \return : slot location in the table correspond to key, 0 if key not found
347 ! **************************************************************************************************
348  PURE FUNCTION nl_hash_table_linear_probe(hash_table, key) RESULT(islot)
349  TYPE(nl_hash_table_obj), INTENT(IN) :: hash_table
350  INTEGER(KIND=int_8), INTENT(IN) :: key
351  INTEGER :: islot
352 
353  INTEGER :: guess
354 
355  ! first guess is mapped by the hash_function
356  guess = nl_hash_table_hash_function(hash_table, key)
357 
358  ! then search for key and stop at first empty slot from guess to
359  ! nmax. using the same linear probe for adding and retrieving
360  ! makes all non-empty keys being put before the first empty slot.
361  DO islot = guess, hash_table%obj%nmax
362  IF ((hash_table%obj%table(islot)%key == key) .OR. &
363  (hash_table%obj%table(islot)%key == empty_key)) RETURN
364  END DO
365 
366  ! if unsuccessful, search from 1 to guess
367  DO islot = 1, guess - 1
368  IF ((hash_table%obj%table(islot)%key == key) .OR. &
369  (hash_table%obj%table(islot)%key == empty_key)) RETURN
370  END DO
371 
372  ! if not found and table is full set islot to 0
373  islot = 0
374  END FUNCTION nl_hash_table_linear_probe
375 
376 ! **************************************************************************************************
377 !> \brief Hash function
378 !> \param hash_table : the nl_hash_table object
379 !> \param key : key to locate
380 !> \return : slot location in the table correspond to key, 0 if key not found
381 ! **************************************************************************************************
382  PURE FUNCTION nl_hash_table_hash_function(hash_table, key) RESULT(hash)
383  TYPE(nl_hash_table_obj), INTENT(IN) :: hash_table
384  INTEGER(KIND=int_8), INTENT(IN) :: key
385  INTEGER :: hash
386 
387  INTEGER(KIND=int_8) :: hash_8, nmax_8, prime_8
388 
389  nmax_8 = int(hash_table%obj%nmax, int_8)
390  prime_8 = int(hash_table%obj%prime, int_8)
391 
392  ! IAND with nmax-1 is equivalent to MOD nmax if nmax is alway a power of 2.
393  hash_8 = iand(key*prime_8, nmax_8 - 1) + 1_int_8
394  hash = int(hash_8)
395  END FUNCTION nl_hash_table_hash_function
396 
397 END MODULE qs_nl_hash_table_types
398 
static unsigned int hash(const dbm_task_t task)
Private hash function based on Szudzik's elegant pairing. Using unsigned int to return a positive num...
static GRID_HOST_DEVICE int idx(const orbital a)
Return coset index of given orbital angular momentum.
Definition: grid_common.h:153
Defines the basic variable types.
Definition: kinds.F:23
integer, parameter, public int_8
Definition: kinds.F:54
Functions which are common to different hash tables Derived from qs_fb_hash_table_types and qs_fb_has...
pure integer function, public hash_table_matching_prime(ii)
Find a prime number equal or larger than ii.
Define the neighbor list data types and the corresponding functionality.
A simple hash table of integer keys, using hash function: H(k) = (k*p) mod n + 1 where: k = key p = a...
subroutine, public nl_hash_table_is_null(hash_table, key, is_null)
Initialises a nl_hash_table object.
subroutine, public nl_hash_table_release(hash_table)
releases the hash table. Note that deallocating tasks stored in the table is the responsibility of th...
subroutine, public nl_hash_table_status(hash_table, nelements, nmax, prime)
outputs the current information about the table
recursive subroutine, public nl_hash_table_add(hash_table, key, val)
Add element to a hash table, auto resize if necessary.
subroutine, public nl_hash_table_get_from_index(hash_table, idx, val)
Retrieve value from a hash table given a specified index.
subroutine, public nl_hash_table_create(hash_table, nmax)
Creates and initialises an empty nl_hash_table object.