hash.F90 Source File


Contents

Source Code


Source Code

#include "macros.h"

module hash

    use FciMCData, only: hash_iter, hash_shift, RandomHash2, HFDet, ll_node
    use bit_rep_data, only: flag_deterministic, test_flag, test_flag_multi, extract_sign
    use bit_reps, only: decode_bit_det
    use Systemdata, only: nel, nBasis
    use CalcData, only: tSemiStochastic
    use constants
    use error_handling_neci, only: stop_all

    implicit none

    interface hash_table_lookup
        module procedure hash_table_lookup_int_32
        module procedure hash_table_lookup_int_64
    end interface

contains

    ! Routine to find the correct position in the hash table.
    pure function FindWalkerHash(orb_array, HashIndexLength) result(hashInd)

        integer, intent(in) :: orb_array(:)
        integer, intent(in) :: HashIndexLength
        integer :: hashInd
        integer :: i
        integer(int64) :: hash
        hash = 0
        do i = 1, size(orb_array)
            hash = (1099511628211_int64 * hash) + &
                   int(RandomHash2(orb_array(i)) * i, int64)
        end do
        hashInd = int(abs(mod(hash, int(HashIndexLength, int64)))) + 1

    end function FindWalkerHash

    ! -- Hash table routines applicable to hash tables that reference lists of
    ! *any* type of data -----------------------------------------------------

    pure subroutine init_hash_table(hash_table)

        ! Take a just-allocated hash table, which must be empty, and
        ! initialise it by nullifying all pointers and setting all entries to
        ! zero.

        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer :: i

        do i = 1, size(hash_table)
            hash_table(i)%ind = 0
            nullify (hash_table(i)%next)
        end do

    end subroutine init_hash_table

    pure subroutine clear_hash_table(hash_table)

        ! Take hash_table and clear it. This is done by nullifying all pointers
        ! in all the linked lists that form the hash table, and setting the
        ! first index to zero.

        type(ll_node), pointer, intent(inout) :: hash_table(:)
        type(ll_node), pointer :: curr, prev
        integer :: i

        ! Loop over all entries corresponding to different hash values.
        do i = 1, size(hash_table)
            ! Point to the second entry in this linked list.
            curr => hash_table(i)%next
            ! Point to the first entry in this linked list.
            prev => hash_table(i)
            ! Set the first index to zero.
            prev%ind = 0
            nullify (prev%next)
            ! Loop over the whole linked list and deallocate all pointers.
            do while (associated(curr))
                prev => curr
                curr => curr%next
                deallocate(prev)
            end do
        end do

        nullify (curr)
        nullify (prev)

    end subroutine clear_hash_table

    subroutine remove_hash_table_entry(hash_table, nI, ind)

        ! Find and remove the entry in hash_table corresponding to nI, which
        ! must have index ind in the hash table. If not found then an error
        ! will be thrown.

        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer, intent(in) :: nI(:)
        integer, intent(in) :: ind

        integer :: hash_val
        type(ll_node), pointer :: prev, curr
        logical :: found
#ifdef DEBUG_
        character(len=*), parameter :: this_routine = "remove_hash_table_entry"
#endif

        ASSERT(all(nI <= nBasis))
        ASSERT(all(nI > 0))

        found = .false.

        ! Find the hash value corresponding to this determinant.
        hash_val = FindWalkerHash(nI, size(hash_table))
        ! Point at the start of the linked list for this hash value.
        curr => hash_table(hash_val)
        prev => null()
        ! Loop over all entries in the linked list until we find the one equal
        ! to ind, the entry that we want to remove.
        do while (associated(curr))
            if (curr%ind == ind) then
                ! If this is the state to be removed.
                found = .true.
                call remove_node(prev, curr)
                exit
            end if
            prev => curr
            curr => curr%next
        end do

        ASSERT(found)

    end subroutine remove_hash_table_entry

    subroutine update_hash_table_ind(hash_table, nI, ind_old, ind_new)

        ! Find and remove the entry in hash_table corresponding to nI, which
        ! must have index ind in the hash table. If not found then an error
        ! will be thrown.

        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer, intent(in) :: nI(:)
        integer, intent(in) :: ind_old, ind_new

        integer :: hash_val
        type(ll_node), pointer :: prev, curr
        logical :: found
#ifdef DEBUG_
        character(len=*), parameter :: this_routine = "update_hash_table_ind"
#endif

        ASSERT(all(nI <= nBasis))
        ASSERT(all(nI > 0))

        found = .false.

        ! Find the hash value corresponding to this determinant.
        hash_val = FindWalkerHash(nI, size(hash_table))
        ! Point at the start of the linked list for this hash value.
        curr => hash_table(hash_val)
        prev => null()
        ! Loop over all entries in the linked list until we find the one equal
        ! to ind, the entry that we want to remove.
        do while (associated(curr))
            if (curr%ind == ind_old) then
                ! If this is the state to be removed.
                found = .true.
                curr%ind = ind_new
                exit
            end if
            prev => curr
            curr => curr%next
        end do

        ASSERT(found)

    end subroutine update_hash_table_ind

    pure subroutine remove_node(prev, curr)

        ! On input, prev should point to the the node before curr in the linked list,
        ! or should point to null if curr is the first node in the list. curr should
        ! point to the node which is to be removed.

        ! On output, both prev and curr will be nullified.

        type(ll_node), pointer, intent(inout) :: prev, curr
        type(ll_node), pointer :: temp_node

        if (associated(prev)) then
            ! If not the first state in the list.
            prev%next => curr%next
            deallocate(curr)
        else
            ! If the first state in the list.
            if (associated(curr%next)) then
                ! If the first but not the only state in the list.
                ! Move the details of the second entry in the list to the
                ! first entry, and then deallocate the second entry.
                curr%ind = curr%next%ind
                temp_node => curr%next
                curr%next => curr%next%next
                deallocate(temp_node)
            else
                ! If the first and only state in the list.
                curr%ind = 0
                curr%next => null()
            end if
        end if

        prev => null()
        curr => null()
        temp_node => null()

    end subroutine remove_node

    pure subroutine add_hash_table_entry(hash_table, ind, hash_val)

        ! Add an entry of ind into hash_table at an index specified by hash_val.

        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer, intent(in) :: ind
        integer, intent(in) :: hash_val

        type(ll_node), pointer :: temp_node

        ! Point to the start of the linked list corresponding to hash_val.
        temp_node => hash_table(hash_val)
        if (temp_node%ind == 0) then
            ! If here then this linked list is empty.
            ! Just need to add the index to the hash table and exit.
            temp_node%ind = ind
        else
            ! If here then there is at least one entry in this linked list.
            ! Cycle to the end of the linked list, and add this new entry on
            ! the end.
            do while (associated(temp_node%next))
                temp_node => temp_node%next
            end do
            allocate(temp_node%next)
            nullify (temp_node%next%next)
            temp_node%next%ind = ind
        end if

        nullify (temp_node)

    end subroutine add_hash_table_entry

    pure subroutine hash_table_lookup_int_32(orbs, targ, max_elem, hash_table, targ_array, ind, hash_val, found)

        ! Perform a search of targ_array for targ, by using hash_table.
        ! Only elements 0:max_elem will be used in comparing targ.
        ! If targ is found then found will be returned as .true. and ind will
        ! contain the index of targ in targ_array. Else, found will be
        ! returned as .false. and ind will be unset.

        integer, intent(in) :: orbs(:)
        integer(int32), intent(in) :: targ(0:)
        integer, intent(in) :: max_elem
        ! Note that hash_table won't actually change, but we need the inout
        ! label to make this routine pure.
        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer(int32), intent(in) :: targ_array(0:, :)
        integer, intent(out) :: ind
        integer, intent(out) :: hash_val
        logical, intent(out) :: found

        type(ll_node), pointer :: temp_node

        found = .false.

        hash_val = FindWalkerHash(orbs, size(hash_table))
        ! Point to the first node in the linked list for this hash value.
        temp_node => hash_table(hash_val)
        ! If temp_node%ind = 0 then there are no entries in targ_array
        ! with this hash, so the target is not in targ_array, so return.
        if (temp_node%ind /= 0) then
            do while (associated(temp_node))
                ! Check using the index stored in temp_node to see if we have
                ! found the searched-for determinant.
                if (all(targ(0:max_elem) == targ_array(0:max_elem, temp_node%ind))) then
                    found = .true.
                    ind = temp_node%ind
                    exit
                end if
                ! Move on to the next determinant with this hash value.
                temp_node => temp_node%next
            end do
        end if

        nullify (temp_node)

    end subroutine hash_table_lookup_int_32

    pure subroutine hash_table_lookup_int_64(orbs, targ, max_elem, hash_table, targ_array, ind, hash_val, found)

        ! Perform a search of targ_array for targ, by using hash_table.
        ! Only elements 0:max_elem will be used in comparing targ.
        ! If targ is found then found will be returned as .true. and ind will
        ! contain the index of targ in targ_array. Else, found will be
        ! returned as .false. and ind will be unset.

        integer, intent(in) :: orbs(:)
        integer(int64), intent(in) :: targ(0:)
        integer, intent(in) :: max_elem
        ! Note that hash_table won't actually change, but we need the inout
        ! label to make this routine pure.
        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer(int64), intent(in) :: targ_array(0:, :)
        integer, intent(out) :: ind
        integer, intent(out) :: hash_val
        logical, intent(out) :: found

        type(ll_node), pointer :: temp_node

        found = .false.

        hash_val = FindWalkerHash(orbs, size(hash_table))
        ! Point to the first node in the linked list for this hash value.
        temp_node => hash_table(hash_val)
        ! If temp_node%ind = 0 then there are no entries in targ_array
        ! with this hash, so the target is not in targ_array, so return.
        if (temp_node%ind /= 0) then
            do while (associated(temp_node))
                ! Check using the index stored in temp_node to see if we have
                ! found the searched-for determinant.
                if (all(targ(0:max_elem) == targ_array(0:max_elem, temp_node%ind))) then
                    found = .true.
                    ind = temp_node%ind
                    exit
                end if
                ! Move on to the next determinant with this hash value.
                temp_node => temp_node%next
            end do
        end if

        nullify (temp_node)

    end subroutine hash_table_lookup_int_64

    ! -- Hash table routines applicable to hash tables that reference lists of
    ! determinants which are stored in the standard ilut form ----------------

    subroutine fill_in_hash_table(hash_table, table_length, walker_list, list_length, ignore_unocc)

        ! This assumes that the input hash table is clear (use clear_hash_table)
        ! and that there are no repeats in walker_list.

        ! If ignore_unocc dets is input as .true. then unoccupied determinants
        ! will not be included in the hash table, unless they are core
        ! determinants.

        use DetBitOps, only: tAccumEmptyDet
        integer, intent(in) :: table_length, list_length
        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer(n_int), intent(in) :: walker_list(0:, :)
        logical, intent(in) :: ignore_unocc

        type(ll_node), pointer :: temp_node
        integer :: i, hash_val, nI(nel), run
        real(dp) :: real_sign(lenof_sign)
        logical :: tCoreDet
#ifdef DEBUG_
        character(*), parameter :: this_routine = "fill_in_hash_table"
#endif

        tCoreDet = .false.

        do i = 1, list_length
            ! If the ignore_unocc option is true then we don't want to include
            ! unoccupied determinants in the hash table, unless they're
            ! deterministic states.
            if (ignore_unocc) then
                if (tSemiStochastic) then
                    tCoreDet = test_flag_multi(walker_list(:, i), flag_deterministic)
                end if
                call extract_sign(walker_list(:, i), real_sign)
                if (IsUnoccDet(real_sign) .and. (.not. tCoreDet) .and. (.not. tAccumEmptyDet(walker_list(:, i)))) cycle
            end if

            call decode_bit_det(nI, walker_list(:, i))
            ! Find the hash value corresponding to this determinant.
            ASSERT(all(nI <= nBasis))
            ASSERT(all(nI > 0))

            hash_val = FindWalkerHash(nI, table_length)

            call add_hash_table_entry(hash_table, i, hash_val)
        end do

    end subroutine fill_in_hash_table

    subroutine rm_unocc_dets_from_hash_table(hash_table, walker_list, list_length)

        use DetBitOps, only: tAccumEmptyDet
        ! This routine loops through all determinants in walker_list removes
        ! all entries from hash_table for determinants which are both
        ! unoccupied and not core determinants.

        type(ll_node), pointer, intent(inout) :: hash_table(:)
        integer(n_int), intent(in) :: walker_list(0:, :)
        integer, intent(in) :: list_length

        integer :: i, hash_val, nI(nel)
        real(dp) :: real_sign(lenof_sign)
        logical :: found, tCoreDet
        type(ll_node), pointer :: temp_node, prev
#ifdef DEBUG_
        character(len=*), parameter :: this_routine = "rm_unocc_dets_from_hash_table"
#endif

        do i = 1, list_length
            call extract_sign(walker_list(:, i), real_sign)
            tCoreDet = .false.
            if (tSemiStochastic) tCoreDet = test_flag_multi(walker_list(:, i), flag_deterministic)
            if (IsUnoccDet(real_sign) .and. (.not. tCoreDet) .and. (.not. tAccumEmptyDet(walker_list(:, i)))) cycle
            found = .false.
            call decode_bit_det(nI, walker_list(:, i))

            ASSERT(all(nI <= nBasis))
            ASSERT(all(nI > 0))

            hash_val = FindWalkerHash(nI, size(hash_table))
            temp_node => hash_table(hash_val)
            prev => null()
            if (.not. temp_node%ind == 0) then
                ! Loop over all entries with this hash value.
                do while (associated(temp_node))
                    if (temp_node%ind == i) then
                        found = .true.
                        call remove_node(prev, temp_node)
                        exit
                    end if
                    ! Move on to the next determinant with this hash value.
                    prev => temp_node
                    temp_node => temp_node%next
                end do
            end if
            ASSERT(found)
        end do

    end subroutine rm_unocc_dets_from_hash_table

end module hash