LCOV - code coverage report
Current view: top level - src - qs_nl_hash_table_types.F (source / functions) Hit Total Coverage
Test: CP2K Regtests (git:4dc10b3) Lines: 101 109 92.7 %
Date: 2024-11-21 06:45:46 Functions: 11 14 78.6 %

          Line data    Source code
       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             : ! **************************************************************************************************
      27             : MODULE qs_nl_hash_table_types
      28             : 
      29             :    USE kinds,                           ONLY: int_8
      30             :    USE qs_hash_table_functions,         ONLY: hash_table_matching_prime
      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
      46             :              nl_hash_table_is_null, &
      47             :              nl_hash_table_status
      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 = -1_int_8
      67             :       TYPE(neighbor_list_task_type), POINTER :: val => NULL()
      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 = -1
      82             :       INTEGER :: nmax = -1
      83             :       INTEGER :: prime = -1
      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      669678 :    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      669678 :       check_ok = nl_hash_table_has_data(hash_table)
     112      669678 :       CPASSERT(check_ok)
     113             : 
     114             :       ! check hash table size, if too small rehash in a larger table
     115      669678 :       IF (hash_table%obj%nelements*ENLARGE_RATIO .GE. hash_table%obj%nmax) THEN
     116        1671 :          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      669678 :       islot = nl_hash_table_linear_probe(hash_table, key)
     121      669678 :       CPASSERT(islot > 0)
     122             : 
     123             :       ! add a new task to the list of tasks with that key
     124      669678 :       IF (hash_table%obj%table(islot)%key == EMPTY_KEY) THEN
     125      136961 :          hash_table%obj%nelements = hash_table%obj%nelements + 1
     126      136961 :          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      669678 :       IF (ASSOCIATED(hash_table%obj%table(islot)%val)) THEN
     131      532717 :          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      669678 :       hash_table%obj%table(islot)%val => val
     136      669678 :    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       22983 :    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       22983 :       check_ok = .NOT. nl_hash_table_has_data(hash_table)
     151       22983 :       CPASSERT(check_ok)
     152       22983 :       ALLOCATE (hash_table%obj)
     153             :       NULLIFY (hash_table%obj%table)
     154       22983 :       hash_table%obj%nmax = 0
     155       22983 :       hash_table%obj%nelements = 0
     156       22983 :       hash_table%obj%prime = 2
     157       22983 :       my_nmax = 1
     158       22983 :       IF (PRESENT(nmax)) my_nmax = nmax
     159       22983 :       CALL nl_hash_table_init(hash_table=hash_table, nmax=my_nmax)
     160             : 
     161       22983 :    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      131048 :    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      131048 :       CPASSERT((idx .GT. 0) .AND. (idx .LE. hash_table%obj%nmax))
     178             : 
     179      131048 :       check_ok = nl_hash_table_has_data(hash_table)
     180      131048 :       CPASSERT(check_ok)
     181             : 
     182      131048 :       val => hash_table%obj%table(idx)%val
     183             : 
     184      131048 :    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     1189038 :    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     1189038 :       res = ASSOCIATED(hash_table%obj)
     196     1189038 :    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       22983 :    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       22983 :       check_ok = nl_hash_table_has_data(hash_table)
     211       22983 :       CPASSERT(check_ok)
     212       22983 :       my_nmax = hash_table%obj%nmax
     213       22983 :       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       22983 :       two_to_power = 1 ! = 2**0
     218       73956 :       DO WHILE (two_to_power .LT. my_nmax)
     219       50973 :          two_to_power = 2*two_to_power
     220             :       END DO
     221       22983 :       my_nmax = two_to_power
     222             : 
     223       22983 :       IF (ASSOCIATED(hash_table%obj%table)) THEN
     224           0 :          IF (SIZE(hash_table%obj%table) .NE. my_nmax) THEN
     225           0 :             DEALLOCATE (hash_table%obj%table)
     226           0 :             ALLOCATE (hash_table%obj%table(my_nmax))
     227             :          END IF
     228             :       ELSE
     229      394225 :          ALLOCATE (hash_table%obj%table(my_nmax))
     230             :       END IF
     231       22983 :       hash_table%obj%nmax = my_nmax
     232       22983 :       hash_table%obj%prime = hash_table_matching_prime(my_nmax)
     233             : 
     234             :       ! initiate element to be "empty"
     235      348259 :       DO ii = 1, hash_table%obj%nmax
     236      325276 :          hash_table%obj%table(ii)%key = EMPTY_KEY
     237      348259 :          NULLIFY (hash_table%obj%table(ii)%val)
     238             :       END DO
     239       22983 :       hash_table%obj%nelements = 0
     240       22983 :    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      319363 :    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      319363 :       check_ok = nl_hash_table_has_data(hash_table)
     256      319363 :       CPASSERT(check_ok)
     257      319363 :       check_ok = (key .LE. hash_table%obj%nmax)
     258      319363 :       CPASSERT(check_ok)
     259             : 
     260      319363 :       is_null = .FALSE.
     261      319363 :       IF (EMPTY_KEY == hash_table%obj%table(key)%key) THEN !.OR.
     262             :          !NULLIFY(hash_table%obj%table(key)%val)
     263      188315 :          is_null = .TRUE.
     264             :       END IF
     265      319363 :    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        1671 :    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        1671 :          DIMENSION(:)                                    :: tmp_table
     280             : 
     281        1671 :       IF (.NOT. nl_hash_table_has_data(hash_table)) THEN
     282           0 :          CALL nl_hash_table_create(hash_table, nmax)
     283             :          RETURN
     284             :       END IF
     285        1671 :       IF (PRESENT(nmax)) THEN
     286        1671 :          my_nmax = MAX(nmax, hash_table%obj%nelements)
     287             :       ELSE
     288           0 :          my_nmax = hash_table%obj%nmax
     289             :       END IF
     290       10926 :       ALLOCATE (tmp_table(hash_table%obj%nmax))
     291        7584 :       tmp_table(:) = hash_table%obj%table(:)
     292        1671 :       CALL nl_hash_table_release(hash_table)
     293        1671 :       CALL nl_hash_table_create(hash_table=hash_table, nmax=my_nmax)
     294        7584 :       DO ii = 1, SIZE(tmp_table)
     295        7584 :          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        5913 :                                    val=tmp_table(ii)%val)
     299             :          END IF
     300             :       END DO
     301        1671 :       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       22983 :    SUBROUTINE nl_hash_table_release(hash_table)
     310             :       TYPE(nl_hash_table_obj), INTENT(INOUT)             :: hash_table
     311             : 
     312       22983 :       IF (ASSOCIATED(hash_table%obj)) THEN
     313       22983 :          IF (ASSOCIATED(hash_table%obj%table)) THEN
     314       22983 :             DEALLOCATE (hash_table%obj%table)
     315             :          END IF
     316       22983 :          DEALLOCATE (hash_table%obj)
     317             :       ELSE
     318           0 :          NULLIFY (hash_table%obj)
     319             :       END IF
     320       22983 :    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       21312 :    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       21312 :       check_ok = nl_hash_table_has_data(hash_table)
     336       21312 :       CPASSERT(check_ok)
     337       21312 :       IF (PRESENT(nelements)) nelements = hash_table%obj%nelements
     338       21312 :       IF (PRESENT(nmax)) nmax = hash_table%obj%nmax
     339       21312 :       IF (PRESENT(prime)) prime = hash_table%obj%prime
     340       21312 :    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      669678 :    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      669678 :       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      862349 :       DO islot = guess, hash_table%obj%nmax
     362      860761 :          IF ((hash_table%obj%table(islot)%key == key) .OR. &
     363        1588 :              (hash_table%obj%table(islot)%key == EMPTY_KEY)) RETURN
     364             :       END DO
     365             : 
     366             :       ! if unsuccessful, search from 1 to guess
     367        1588 :       DO islot = 1, guess - 1
     368        1588 :          IF ((hash_table%obj%table(islot)%key == key) .OR. &
     369           0 :              (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      669678 :       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      669678 :    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      669678 :       nmax_8 = INT(hash_table%obj%nmax, int_8)
     390      669678 :       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      669678 :       hash_8 = IAND(key*prime_8, nmax_8 - 1) + 1_int_8
     394      669678 :       hash = INT(hash_8)
     395      669678 :    END FUNCTION nl_hash_table_hash_function
     396             : 
     397           0 : END MODULE qs_nl_hash_table_types
     398             : 

Generated by: LCOV version 1.15