(git:1f285aa)
routine_map.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 MODULE routine_map
9 #include "../base/base_uses.f90"
10 
11  IMPLICIT NONE
12  PRIVATE
13 
14 
15 ! **************************************************************************************************
16 !> \brief A hash map (also known as hashtable or dictionary).
17 !> Internally the hash map uses an array to holds its data.
18 !> If this array reaches a load-factor of 75%, a new array with twice the
19 !> size will be allocated and the items are then copied over.
20 !> This ensures that the dictionary will perform operations in O(1).
21 !> \par History
22 !> 12.2012 first version [Ole Schuett]
23 !> 08.2019 refactored for Fypp [Ole Schuett]
24 !> \author Ole Schuett
25 ! ***************************************************************************************************
26 
27  PUBLIC :: routine_map_init
28  PUBLIC :: routine_map_items
29  PUBLIC :: routine_map_haskey
30  PUBLIC :: routine_map_set
31  PUBLIC :: routine_map_get
32  PUBLIC :: routine_map_size
33  PUBLIC :: routine_map_destroy
34  PUBLIC :: routine_map_type
35  PUBLIC :: routine_map_item_type
36 
37 !this is an internal type
38 !Calculating hashes might be expensive, therefore they are stored
39 !for use during change_capacity().
40  TYPE private_item_type
41  PRIVATE
42  CHARACTER(LEN=default_string_length) :: key = ""
43  INTEGER(kind=int_4) :: value = 0_int_4
44  INTEGER(KIND=int_8) :: hash = 0_int_8
45  TYPE(private_item_type), POINTER :: next => null()
46  END TYPE private_item_type
47 
48 !this is an internal type
49  TYPE private_item_p_type
50  PRIVATE
51  TYPE(private_item_type), POINTER :: p => null()
52  END TYPE private_item_p_type
53 
54 ! this is the public type, which holds a hash map instance
55  TYPE routine_map_type
56  PRIVATE
57  TYPE(private_item_p_type), DIMENSION(:), POINTER :: buckets => null()
58  INTEGER :: size = -1
59  END TYPE routine_map_type
60 
61 ! this is a public type, its returned by routine_map_items()
62  TYPE routine_map_item_type
63  CHARACTER(LEN=default_string_length) :: key = ""
64  INTEGER(kind=int_4) :: value = 0_int_4
65  END TYPE routine_map_item_type
66 
67  CONTAINS
68 
69 
70 ! **************************************************************************************************
71 !> \brief Allocates the internal data-structures of the given hash map.
72 !> \param hash_map ...
73 !> \param initial_capacity The initial size of the internal array (default=11).
74 !> \author Ole Schuett
75 ! **************************************************************************************************
76  SUBROUTINE routine_map_init(hash_map, initial_capacity)
77  TYPE(routine_map_type), INTENT(inout) :: hash_map
78  INTEGER, INTENT(in), OPTIONAL :: initial_capacity
79 
80  INTEGER :: initial_capacity_
81 
82  IF (PRESENT(initial_capacity)) THEN
83  initial_capacity_ = initial_capacity
84  ELSE
85  initial_capacity_ = 11
86  END IF
87 
88  IF (initial_capacity_ < 1) &
89  cpabort("initial_capacity < 1")
90 
91  IF (ASSOCIATED(hash_map%buckets)) &
92  cpabort("hash map is already initialized.")
93 
94  ALLOCATE (hash_map%buckets(initial_capacity_))
95  hash_map%size = 0
96 
97  END SUBROUTINE routine_map_init
98 
99 ! **************************************************************************************************
100 !> \brief Test if the given hash map has been initialized.
101 !> \param hash_map ...
102 !> \return ...
103 !> \author Ole Schuett
104 ! **************************************************************************************************
105  FUNCTION routine_map_isready(hash_map) RESULT(res)
106  TYPE(routine_map_type), INTENT(inout) :: hash_map
107  LOGICAL :: res
108  res = ASSOCIATED(hash_map%buckets)
109  END FUNCTION routine_map_isready
110 
111 ! **************************************************************************************************
112 !> \brief Deallocated the internal data-structures if the given hash map.
113 !> Caution: If the stored keys or values are pointers, their targets will
114 !> not get deallocated by this routine.
115 !> \param hash_map ...
116 !> \author Ole Schuett
117 ! **************************************************************************************************
118  SUBROUTINE routine_map_destroy(hash_map)
119  TYPE(routine_map_type), INTENT(inout) :: hash_map
120  TYPE(private_item_type), POINTER :: item, prev_item
121  INTEGER :: i
122 
123  cpassert(ASSOCIATED(hash_map%buckets))
124 
125  DO i = 1, size(hash_map%buckets)
126  item => hash_map%buckets(i)%p
127  DO WHILE (ASSOCIATED(item))
128  prev_item => item
129  item => item%next
130  DEALLOCATE (prev_item)
131  END DO
132  END DO
133 
134  DEALLOCATE (hash_map%buckets)
135  hash_map%size = -1
136  END SUBROUTINE routine_map_destroy
137 
138 ! **************************************************************************************************
139 !> \brief Stores, and possibly overwrites, a given value under a given key.
140 !> \param hash_map ...
141 !> \param key ...
142 !> \param value ...
143 !> \author Ole Schuett
144 ! **************************************************************************************************
145  SUBROUTINE routine_map_set(hash_map, key, value)
146  TYPE(routine_map_type), INTENT(inout) :: hash_map
147  CHARACTER(LEN=default_string_length), INTENT(in) :: key
148  INTEGER(kind=int_4), INTENT(in) :: value
149  INTEGER(KIND=int_8) :: hash
150  cpassert(ASSOCIATED(hash_map%buckets))
151 
152  hash = routine_map_hash_function(key)
153  CALL routine_map_set_hashed(hash_map, key, value, hash)
154  END SUBROUTINE routine_map_set
155 
156 ! **************************************************************************************************
157 !> \brief Common code used internally by routine_map_set() and routine_map_change_capacity().
158 !> \param hash_map ...
159 !> \param key ...
160 !> \param value ...
161 !> \param hash ...
162 !> \author Ole Schuett
163 ! **************************************************************************************************
164  RECURSIVE SUBROUTINE routine_map_set_hashed(hash_map, key, value, hash)
165  TYPE(routine_map_type), INTENT(inout) :: hash_map
166  CHARACTER(LEN=default_string_length), intent(in) :: key
167  INTEGER(kind=int_4), intent(in) :: value
168  INTEGER(KIND=int_8), intent(in) :: hash
169  TYPE(private_item_type), POINTER :: item, new_item
170  INTEGER(KIND=int_8) :: idx
171 
172  idx = mod(hash, int(size(hash_map%buckets), kind=int_8)) + 1
173 
174  ! if already in hash map just update its value
175  item => hash_map%buckets(idx)%p
176  DO WHILE (ASSOCIATED(item))
177  IF (item%hash == hash) THEN
178  IF (routine_map_keys_equal(item%key, key)) THEN
179  item%value =value
180  RETURN
181  END IF
182  END IF
183  item => item%next
184  END DO
185 
186  ! check load-factor
187  IF (4*hash_map%size > 3*size(hash_map%buckets)) THEN ! load-factor > 75%
188  call routine_map_change_capacity(hash_map, 2*size(hash_map%buckets)) !double capacity
189  idx = mod(hash, int(size(hash_map%buckets), kind=int_8)) + 1
190  END IF
191 
192  ! create a new item
193  allocate (new_item)
194  new_item%hash = hash
195  new_item%key =key
196  new_item%value =value
197  new_item%next => hash_map%buckets(idx)%p
198  hash_map%buckets(idx)%p => new_item
199  hash_map%size = hash_map%size + 1
200 
201  END SUBROUTINE routine_map_set_hashed
202 
203 ! **************************************************************************************************
204 !> \brief Internal routine for changing the hash map's capacity.
205 !> \param hash_map ...
206 !> \param new_capacity ...
207 !> \author Ole Schuett
208 ! **************************************************************************************************
209  RECURSIVE SUBROUTINE routine_map_change_capacity(hash_map, new_capacity)
210  TYPE(routine_map_type), INTENT(inout) :: hash_map
211  INTEGER, INTENT(in) :: new_capacity
212  INTEGER :: i, old_size, new_cap
213  TYPE(private_item_type), POINTER :: item, prev_item
214  TYPE(private_item_p_type), DIMENSION(:), POINTER :: old_buckets
215  new_cap = new_capacity
216  ! pre checks
217  IF (new_cap > huge(i)) THEN
218  IF (size(hash_map%buckets) == huge(i)) RETURN ! reached maximum - stay there.
219  new_cap = huge(i) ! grow as far as possible
220  END IF
221  cpassert(new_cap >= 1)
222  cpassert(4*hash_map%size < 3*new_cap)
223 
224  old_size = hash_map%size
225  old_buckets => hash_map%buckets
226  ALLOCATE (hash_map%buckets(new_capacity))
227  hash_map%size = 0
228  DO i = 1, size(old_buckets)
229  item => old_buckets(i)%p
230  DO WHILE (ASSOCIATED(item))
231  CALL routine_map_set_hashed(hash_map, item%key, item%value, item%hash)
232  prev_item => item
233  item => item%next
234  DEALLOCATE (prev_item)
235  END DO
236  END DO
237 
238  DEALLOCATE (old_buckets)
239 
240  cpassert(old_size == hash_map%size)
241  END SUBROUTINE routine_map_change_capacity
242 
243 ! **************************************************************************************************
244 !> \brief Gets a value for a given key from the hash map.
245 !> If the key is not found the default_value will be returned.
246 !> If the key is not found and default_value was not provided the program stops.
247 !> \param hash_map ...
248 !> \param key ...
249 !> \param default_value ...
250 !> \return ...
251 !> \author Ole Schuett
252 ! **************************************************************************************************
253  FUNCTION routine_map_get(hash_map, key, default_value) RESULT(value)
254  TYPE(routine_map_type), INTENT(in) :: hash_map
255  CHARACTER(LEN=default_string_length), INTENT(in) :: key
256  INTEGER(kind=int_4), INTENT(in), OPTIONAL :: default_value
257  INTEGER(kind=int_4) :: value
258  TYPE(private_item_type), POINTER :: item
259  INTEGER(KIND=int_8) :: hash, idx
260 
261  cpassert(ASSOCIATED(hash_map%buckets))
262 
263  hash = routine_map_hash_function(key)
264  idx = mod(hash, int(size(hash_map%buckets), kind=int_8)) + 1
265 
266  item => hash_map%buckets(idx)%p
267  DO WHILE (ASSOCIATED(item))
268  IF (item%hash == hash) THEN
269  IF (routine_map_keys_equal(item%key, key)) THEN
270  value =item%value
271  RETURN
272  END IF
273  END IF
274  item => item%next
275  END DO
276 
277  IF (PRESENT(default_value)) THEN
278  value =default_value
279  RETURN
280  END IF
281 
282  cpabort("Key not found.")
283  END FUNCTION routine_map_get
284 
285 ! **************************************************************************************************
286 !> \brief Remove the value for a given key from the hash map.
287 !> If the key is not found the program stops.
288 !> \param hash_map ...
289 !> \param key ...
290 !> \author Ole Schuett
291 ! **************************************************************************************************
292  SUBROUTINE routine_map_del(hash_map, key)
293  TYPE(routine_map_type), INTENT(inout) :: hash_map
294  CHARACTER(LEN=default_string_length), INTENT(in) :: key
295  TYPE(private_item_type), POINTER :: item, prev_item
296  INTEGER(KIND=int_8) :: hash, idx
297 
298  cpassert(ASSOCIATED(hash_map%buckets))
299 
300  hash = routine_map_hash_function(key)
301  idx = mod(hash, int(size(hash_map%buckets), kind=int_8)) + 1
302 
303  item => hash_map%buckets(idx)%p
304  prev_item => null()
305  DO WHILE (ASSOCIATED(item))
306  IF (item%hash == hash) THEN
307  IF (routine_map_keys_equal(item%key, key)) THEN
308  IF (ASSOCIATED(prev_item)) THEN
309  prev_item%next => item%next
310  ELSE
311  hash_map%buckets(idx)%p => item%next
312  END IF
313  DEALLOCATE (item)
314  hash_map%size = hash_map%size - 1
315  RETURN
316  END IF
317  END IF
318  prev_item => item
319  item => item%next
320  END DO
321 
322  cpabort("Key not found.")
323  END SUBROUTINE routine_map_del
324 
325 ! **************************************************************************************************
326 !> \brief Returns the number of key/value-items currently stored in the hash map.
327 !> \param hash_map ...
328 !> \return ...
329 !> \author Ole Schuett
330 ! **************************************************************************************************
331  FUNCTION routine_map_size(hash_map) RESULT(size)
332  TYPE(routine_map_type), INTENT(IN) :: hash_map
333  INTEGER :: size
334 
335  cpassert(ASSOCIATED(hash_map%buckets))
336  size = hash_map%size
337  END FUNCTION routine_map_size
338 
339 ! **************************************************************************************************
340 !> \brief Checks whether a given key is currently stored in the hash_map.
341 !> \param hash_map ...
342 !> \param key ...
343 !> \return ...
344 !> \author Ole Schuett
345 ! **************************************************************************************************
346  FUNCTION routine_map_haskey(hash_map, key) RESULT(res)
347  TYPE(routine_map_type), INTENT(IN) :: hash_map
348  CHARACTER(LEN=default_string_length), INTENT(IN) :: key
349  LOGICAL :: res
350  TYPE(private_item_type), POINTER :: item
351  INTEGER(KIND=int_8) :: hash, idx
352 
353  cpassert(ASSOCIATED(hash_map%buckets))
354 
355  res = .false.
356  IF (hash_map%size == 0) RETURN
357 
358  hash = routine_map_hash_function(key)
359  idx = mod(hash, int(size(hash_map%buckets), kind=int_8)) + 1
360 
361  item => hash_map%buckets(idx)%p
362  DO WHILE (ASSOCIATED(item))
363  IF (item%hash == hash) THEN
364  IF (routine_map_keys_equal(item%key, key)) THEN
365  res = .true.
366  return
367  END IF
368  END IF
369  item => item%next
370  END DO
371 
372  END FUNCTION routine_map_haskey
373 
374 ! **************************************************************************************************
375 !> \brief Returns a pointer to an array of all key/value-items stored in the hash map.
376 !> Caution: The caller is responsible for deallocating targeted array after usage.
377 !> \param hash_map ...
378 !> \return ...
379 !> \author Ole Schuett
380 ! **************************************************************************************************
381  FUNCTION routine_map_items(hash_map) RESULT(items)
382  TYPE(routine_map_type), INTENT(IN) :: hash_map
383  TYPE(routine_map_item_type), DIMENSION(:), POINTER :: items
384 
385  TYPE(private_item_type), POINTER :: item
386  INTEGER :: i, j
387 
388  cpassert(ASSOCIATED(hash_map%buckets))
389 
390  ALLOCATE (items(hash_map%size))
391  j = 1
392  DO i = 1, size(hash_map%buckets)
393  item => hash_map%buckets(i)%p
394  DO WHILE (ASSOCIATED(item))
395  items(j)%key =item%key
396  items(j)%value =item%value
397  j = j + 1
398  item => item%next
399  END DO
400  END DO
401 
402  cpassert(j == hash_map%size + 1)
403  END FUNCTION routine_map_items
404 
405 ! **************************************************************************************************
406 !> \brief Copies all key/values-items from one hash map to another.
407 !> Afterwards hash_map will contain all items from the from_hash_map and
408 !> additionally all its previous items, which were not overwritten.
409 !> The two hash maps have to be of the same type.
410 !> \param hash_map destination of items
411 !> \param from_hash_map source of items - will not be change
412 !> \author Ole Schuett
413 ! **************************************************************************************************
414  SUBROUTINE routine_map_update(hash_map, from_hash_map)
415  TYPE(routine_map_type), INTENT(inout) :: hash_map
416  TYPE(routine_map_type), INTENT(in) :: from_hash_map
417  TYPE(routine_map_item_type), DIMENSION(:), POINTER :: from_items
418  INTEGER :: i
419 
420  cpassert(ASSOCIATED(hash_map%buckets))
421  cpassert(ASSOCIATED(from_hash_map%buckets))
422 
423  from_items => routine_map_items(from_hash_map)
424  DO i = 1, size(from_items)
425  CALL routine_map_set(hash_map, from_items(i)%key, from_items(i)%value)
426  END DO
427  DEALLOCATE (from_items)
428  END SUBROUTINE routine_map_update
429 
430 
431 ! **************************************************************************************************
432 ! This is joaat_hash from string_table.F
433 !
434 !> \brief generates the hash of a given string
435 !> \param key a string of any length
436 !> \return ...
437 !> \par History
438 !> 09.2006 created [Joost VandeVondele]
439 !> 12.2012 copied and adopted [ole]
440 !> \note
441 !> http://en.wikipedia.org/wiki/Hash_table
442 !> http://www.burtleburtle.net/bob/hash/doobs.html
443 ! **************************************************************************************************
444  PURE FUNCTION routine_map_hash_function(key) RESULT(hash)
445  CHARACTER(LEN=*), INTENT(IN) :: key
446  INTEGER(KIND=int_8) :: hash
447 
448  INTEGER(KIND=int_8), PARAMETER :: b32 = 2_int_8**32 - 1_int_8
449 
450  INTEGER :: i
451 
452  hash = 0_int_8
453  DO i = 1, len(key)
454  hash = iand(hash + ichar(key(i:i)), b32)
455  hash = iand(hash + iand(ishft(hash, 10), b32), b32)
456  hash = iand(ieor(hash, iand(ishft(hash, -6), b32)), b32)
457  END DO
458  hash = iand(hash + iand(ishft(hash, 3), b32), b32)
459  hash = iand(ieor(hash, iand(ishft(hash, -11), b32)), b32)
460  hash = iand(hash + iand(ishft(hash, 15), b32), b32)
461  END FUNCTION routine_map_hash_function
462 
463 ! **************************************************************************************************
464 !> \brief ...
465 !> \param key ...
466 !> \return ...
467 ! **************************************************************************************************
468  PURE FUNCTION routine_map_keys_equal(key1, key2) RESULT(res)
469  CHARACTER(LEN=*), INTENT(IN) :: key1, key2
470  LOGICAL :: res
471 
472  res = (key1 == key2)
473  END FUNCTION routine_map_keys_equal
474 
475 END MODULE routine_map
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
integer, parameter, public default_string_length
Definition: kinds.F:57
integer, parameter, public int_4
Definition: kinds.F:51
integer(kind=int_4) function, public routine_map_get(hash_map, key, default_value)
Gets a value for a given key from the hash map. If the key is not found the default_value will be ret...
Definition: routine_map.F:254
integer function, public routine_map_size(hash_map)
Returns the number of key/value-items currently stored in the hash map.
Definition: routine_map.F:332
subroutine, public routine_map_init(hash_map, initial_capacity)
Allocates the internal data-structures of the given hash map.
Definition: routine_map.F:77
logical function, public routine_map_haskey(hash_map, key)
Checks whether a given key is currently stored in the hash_map.
Definition: routine_map.F:347
type(routine_map_item_type) function, dimension(:), pointer, public routine_map_items(hash_map)
Returns a pointer to an array of all key/value-items stored in the hash map. Caution: The caller is r...
Definition: routine_map.F:382
subroutine, public routine_map_destroy(hash_map)
Deallocated the internal data-structures if the given hash map. Caution: If the stored keys or values...
Definition: routine_map.F:119
subroutine, public routine_map_set(hash_map, key, value)
Stores, and possibly overwrites, a given value under a given key.
Definition: routine_map.F:146