!> This module contains functions for GAS that are not bound !> to a specific GAS excitation generator. module gasci_util use constants, only: n_int, dp, int64 use SystemData, only: nEl, nBasis use gasci, only: GASSpec_t, LocalGASSpec_t, CumulGASSpec_t, GAS_specification use gasci_supergroup_index, only: get_supergroups use orb_idx_mod, only: SpinProj_t, calc_spin_raw, sum, alpha, beta use sort_mod, only: sort use excitation_types, only: Excite_1_t, Excite_2_t, excite, get_last_tgt, & set_last_tgt, UNKNOWN, canonicalize use util_mod, only: lex_leq, cumsum, operator(.div.), near_zero, binary_search_first_ge, & operator(.isclose.), custom_findloc, choose_i64, swap use dSFMT_interface, only: genrand_real2_dSFMT use DetBitOps, only: ilut_lt, ilut_gt, EncodeBitDet use bit_rep_data, only: NIfTot, NIfD use sltcnd_mod, only: sltcnd_excit use sets_mod, only: disjoint, is_sorted use growing_buffers, only: buffer_int_2D_t, buffer_int_1D_t use bit_reps, only: decode_bit_det implicit none private public :: get_available_singles, get_available_doubles, & gen_all_excits, get_n_SDs public :: get_cumulative_list, draw_from_cum_list public :: write_GAS_info, t_output_GAS_sizes interface get_cumulative_list module procedure get_cumulative_list_Excite_1_t module procedure get_cumulative_list_Excite_2_t end interface logical :: t_output_GAS_sizes = .false. contains !> @brief !> Get all single excitated determinants from det_I that are allowed under GAS constraints. pure function get_available_singles(GAS_spec, det_I) result(singles_exc_list) class(GASSpec_t), intent(in) :: GAS_spec integer, intent(in) :: det_I(:) integer, allocatable :: singles_exc_list(:, :) !! Dimension is (nEl, n_configurations) character(*), parameter :: this_routine = 'get_available_singles' integer, allocatable :: possible_holes(:) integer :: i, j, src1, tgt1 type(SpinProj_t) :: m_s_1 type(buffer_int_2D_t) :: buffer #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (GAS_spec%contains_conf(det_I))) then call stop_all (this_routine, "Assert fail: GAS_spec%contains_conf(det_I)") end if end block #endif call buffer%init(size(det_I)) do i = 1, size(det_I) src1 = det_I(i) m_s_1 = calc_spin_raw(src1) possible_holes = GAS_spec%get_possible_holes(& det_I, add_holes=det_I(i:i), & excess=-m_s_1) do j = 1, size(possible_holes) tgt1 = possible_holes(j) call buffer%push_back(excite(det_I, Excite_1_t(src1, tgt1))) end do end do call buffer%dump_reset(singles_exc_list) ! merge_sort block integer, dimension(:, :), allocatable :: tmp integer :: current_size, left, mid, right integer, parameter :: along = 2, & bubblesort_size = 20 associate(X => singles_exc_list) ! Determine good number of starting splits block integer :: n_splits n_splits = 1 do while (size(X, along) / n_splits + merge(0, 1, mod(size(X, along), n_splits) == 0) > bubblesort_size) n_splits = n_splits + 1 end do current_size = size(X, along) / n_splits + merge(0, 1, mod(size(X, along), n_splits) == 0) end block ! Reuse this variable as tmp for swap in bubble_sort ! and for merge in merge_sort. block integer :: max_buffer_size, n_merges n_merges = ceiling(log(real(size(X, along)) / real(current_size)) / log(2.0)) max_buffer_size = current_size * merge(2**(n_merges - 1), 1, n_merges >= 1) allocate(tmp(size(X, 1), max_buffer_size)) end block ! Bubble sort bins of size `bubblesort_size`. do left = lbound(X, along), ubound(X, along), current_size right = min(left + bubblesort_size - 1, ubound(X, along)) ! bubblesort block integer :: n, i associate(V => X(:, left : right)) do n = ubound(V, 2), lbound(V, 2) + 1, -1 do i = lbound(V, 2), ubound(V, 2) - 1 if (.not. lex_leq(V(:, i), V(:, i + 1))) then ! swap block associate(tmp => tmp(:, 1)) tmp = V(:, i) V(:, i) = V(:, i + 1) V(:, i + 1) = tmp end associate end block end if end do end do end associate end block end do do while (current_size < size(X, along)) do left = lbound(X, along), ubound(X, along), 2 * current_size right = min(left + 2 * current_size - 1, ubound(X, along)) mid = min(left + current_size, right) - 1 tmp(:, : mid - left + 1) = X(:, left : mid) ! merge block integer :: i, j, k associate(A => tmp(:, : mid - left + 1), B => X(:, mid + 1 : right), C => X(:, left : right)) if (size(A) + size(B) > size(C)) then error stop end if i = lbound(A, 2) j = lbound(B, 2) do k = lbound(C, 2), ubound(C, 2) if (i <= ubound(A, 2) .and. j <= ubound(B, 2)) then if ( lex_leq(A(:, i), B(:, j))) then C(:, k) = A(:, i) i = i + 1 else C(:, k) = B(:, j) j = j + 1 end if else if (i <= ubound(A, 2)) then C(:, k) = A(:, i) i = i + 1 else if (j <= ubound(B, 2)) then C(:, k) = B(:, j) j = j + 1 end if end do end associate end block end do current_size = 2 * current_size end do end associate end block end function !> @brief !> Get all double excitated determinants from det_I that are allowed under GAS constraints. pure function get_available_doubles(GAS_spec, det_I) result(doubles_exc_list) class(GASSpec_t), intent(in) :: GAS_spec integer, intent(in) :: det_I(:) integer, allocatable :: doubles_exc_list(:, :) character(*), parameter :: this_routine = 'get_available_doubles' integer, allocatable :: first_pick_possible_holes(:), second_pick_possible_holes(:), deleted(:) integer :: i, j, k, l, src1, src2, tgt1, tgt2 type(SpinProj_t) :: m_s_1 type(buffer_int_2D_t) :: buffer #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (GAS_spec%contains_conf(det_I))) then call stop_all (this_routine, "Assert fail: GAS_spec%contains_conf(det_I)") end if end block #endif call buffer%init(size(det_I)) do i = 1, size(det_I) do j = i + 1, size(det_I) src1 = det_I(i) src2 = det_I(j) deleted = det_I([i, j]) first_pick_possible_holes = GAS_spec%get_possible_holes(det_I, & add_holes=deleted, excess=-sum(calc_spin_raw(deleted)), & n_total=2) #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (disjoint(first_pick_possible_holes, det_I))) then call stop_all (this_routine, "Assert fail: disjoint(first_pick_possible_holes, det_I)") end if end block #endif do k = 1, size(first_pick_possible_holes) tgt1 = first_pick_possible_holes(k) m_s_1 = calc_spin_raw(tgt1) #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (any(m_s_1 == [alpha, beta]))) then call stop_all (this_routine, "Assert fail: any(m_s_1 == [alpha, beta])") end if end block #endif second_pick_possible_holes = GAS_spec%get_possible_holes(& det_I, add_holes=deleted, & add_particles=[tgt1], & n_total=1, excess=m_s_1 - sum(calc_spin_raw(deleted))) #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (disjoint(second_pick_possible_holes, [tgt1]))) then call stop_all (this_routine, "Assert fail: disjoint(second_pick_possible_holes, [tgt1])") end if end block #endif #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (disjoint(second_pick_possible_holes, det_I))) then call stop_all (this_routine, "Assert fail: disjoint(second_pick_possible_holes, det_I)") end if end block #endif do l = 1, size(second_pick_possible_holes) tgt2 = second_pick_possible_holes(l) call buffer%push_back(excite(det_I, canonicalize(Excite_2_t(src1, tgt1, src2, tgt2)))) end do end do end do end do call buffer%dump_reset(doubles_exc_list) ! merge_sort block integer, dimension(:, :), allocatable :: tmp integer :: current_size, left, mid, right integer, parameter :: along = 2, & bubblesort_size = 20 associate(X => doubles_exc_list) ! Determine good number of starting splits block integer :: n_splits n_splits = 1 do while (size(X, along) / n_splits + merge(0, 1, mod(size(X, along), n_splits) == 0) > bubblesort_size) n_splits = n_splits + 1 end do current_size = size(X, along) / n_splits + merge(0, 1, mod(size(X, along), n_splits) == 0) end block ! Reuse this variable as tmp for swap in bubble_sort ! and for merge in merge_sort. block integer :: max_buffer_size, n_merges n_merges = ceiling(log(real(size(X, along)) / real(current_size)) / log(2.0)) max_buffer_size = current_size * merge(2**(n_merges - 1), 1, n_merges >= 1) allocate(tmp(size(X, 1), max_buffer_size)) end block ! Bubble sort bins of size `bubblesort_size`. do left = lbound(X, along), ubound(X, along), current_size right = min(left + bubblesort_size - 1, ubound(X, along)) ! bubblesort block integer :: n, i associate(V => X(:, left : right)) do n = ubound(V, 2), lbound(V, 2) + 1, -1 do i = lbound(V, 2), ubound(V, 2) - 1 if (.not. lex_leq(V(:, i), V(:, i + 1))) then ! swap block associate(tmp => tmp(:, 1)) tmp = V(:, i) V(:, i) = V(:, i + 1) V(:, i + 1) = tmp end associate end block end if end do end do end associate end block end do do while (current_size < size(X, along)) do left = lbound(X, along), ubound(X, along), 2 * current_size right = min(left + 2 * current_size - 1, ubound(X, along)) mid = min(left + current_size, right) - 1 tmp(:, : mid - left + 1) = X(:, left : mid) ! merge block integer :: i, j, k associate(A => tmp(:, : mid - left + 1), B => X(:, mid + 1 : right), C => X(:, left : right)) if (size(A) + size(B) > size(C)) then error stop end if i = lbound(A, 2) j = lbound(B, 2) do k = lbound(C, 2), ubound(C, 2) if (i <= ubound(A, 2) .and. j <= ubound(B, 2)) then if ( lex_leq(A(:, i), B(:, j))) then C(:, k) = A(:, i) i = i + 1 else C(:, k) = B(:, j) j = j + 1 end if else if (i <= ubound(A, 2)) then C(:, k) = A(:, i) i = i + 1 else if (j <= ubound(B, 2)) then C(:, k) = B(:, j) j = j + 1 end if end do end associate end block end do current_size = 2 * current_size end do end associate end block remove_double_appearances : block integer, allocatable :: tmp_buffer(:, :) allocate(tmp_buffer(size(doubles_exc_list, 1), size(doubles_exc_list, 2))) j = 1 tmp_buffer(:, j) = doubles_exc_list(:, 1) do i = 2, size(doubles_exc_list, 2) if (any(doubles_exc_list(:, i - 1) /= doubles_exc_list(:, i))) then j = j + 1 tmp_buffer(:, j) = doubles_exc_list(:, i) end if end do doubles_exc_list = tmp_buffer(:, : j) end block remove_double_appearances end function !> @brief !> Get all excitated determinants from !> det_I that are allowed under GAS constraints. !> !> @param[in] GAS_spec GAS specification !> @param[in] nI Starting determinant !> @param[out] n_excits Number of determinants !> @param[out] det_list Allocatable array of determinants in ilut format !> @param[in] ic Optional input for excitation level (ic=1 => singles, ic=2 => doubles) !> If ommited generate all. subroutine gen_all_excits(GAS_spec, nI, n_excits, det_list, ic) class(GASSpec_t), intent(in) :: GAS_spec integer, intent(in) :: nI(:) integer, intent(out) :: n_excits integer(n_int), intent(out), allocatable :: det_list(:,:) integer, intent(in), optional :: ic integer, allocatable :: singles(:, :), doubles(:, :) integer :: i, j if (present(ic)) then select case(ic) case(1) singles = get_available_singles(GAS_spec, nI) allocate(doubles(0, 0)) case(2) allocate(singles(0, 0)) doubles = get_available_doubles(GAS_spec, nI) end select else singles = get_available_singles(GAS_spec, nI) doubles = get_available_doubles(GAS_spec, nI) end if n_excits = size(singles, 2) + size(doubles, 2) allocate(det_list(0:niftot, n_excits)) j = 1 do i = 1, size(singles, 2) call EncodeBitDet(singles(:, i), det_list(:, j)) j = j + 1 end do do i = 1, size(doubles, 2) call EncodeBitDet(doubles(:, i), det_list(:, j)) j = j + 1 end do call sort(det_list, ilut_lt, ilut_gt) end subroutine gen_all_excits !> @brief !> Build up a cumulative list of matrix elements. !> !> @details !> Calculate the matrix elements for the possible excitations from det_I !> to the possible holes using the incomplete defined excitation. !> !> @param[in] det_I, Reference determinant in "nI-format". !> @param[in] incomplete_exc, An excitation where the last target is unknown. !> @param[in] possible_holes, Possible holes for the last target. function get_cumulative_list_Excite_1_t(det_I, incomplete_exc, possible_holes) result(cSum) integer, intent(in) :: det_I(:) type(Excite_1_t), intent(in) :: incomplete_exc integer, intent(in) :: possible_holes(:) real(dp) :: cSum(size(possible_holes)) character(*), parameter :: this_routine = 'get_cumulative_list_Excite_1_t' real(dp) :: previous type(Excite_1_t) :: exc integer :: i #ifdef DEBUG_ block use util_mod, only: stop_all use constants, only: stderr if (.not. (get_last_tgt(exc) == UNKNOWN)) then write(stderr, *) "" write(stderr, *) "Assertion get_last_tgt(exc) == UNKNOWN" write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_uti& &l.fpp:224" call stop_all (this_routine, "Assert fail: get_last_tgt(exc) == UNKNOWN") end if end block #endif exc = incomplete_exc ! build the cumulative list of matrix elements <src|H|tgt> previous = 0.0_dp do i = 1, size(possible_holes) call set_last_tgt(exc, possible_holes(i)) cSum(i) = abs(sltcnd_excit(det_I, canonicalize(exc), .false.)) + previous previous = cSum(i) end do ! Normalize if (near_zero(cSum(size(cSum)))) then cSum(:) = 0.0_dp else cSum(:) = cSum(:) / cSum(size(cSum)) end if end function get_cumulative_list_Excite_1_t !> @brief !> Build up a cumulative list of matrix elements. !> !> @details !> Calculate the matrix elements for the possible excitations from det_I !> to the possible holes using the incomplete defined excitation. !> !> @param[in] det_I, Reference determinant in "nI-format". !> @param[in] incomplete_exc, An excitation where the last target is unknown. !> @param[in] possible_holes, Possible holes for the last target. function get_cumulative_list_Excite_2_t(det_I, incomplete_exc, possible_holes) result(cSum) integer, intent(in) :: det_I(:) type(Excite_2_t), intent(in) :: incomplete_exc integer, intent(in) :: possible_holes(:) real(dp) :: cSum(size(possible_holes)) character(*), parameter :: this_routine = 'get_cumulative_list_Excite_2_t' real(dp) :: previous type(Excite_2_t) :: exc integer :: i #ifdef DEBUG_ block use util_mod, only: stop_all use constants, only: stderr if (.not. (get_last_tgt(exc) == UNKNOWN)) then write(stderr, *) "" write(stderr, *) "Assertion get_last_tgt(exc) == UNKNOWN" write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_uti& &l.fpp:224" call stop_all (this_routine, "Assert fail: get_last_tgt(exc) == UNKNOWN") end if end block #endif exc = incomplete_exc ! build the cumulative list of matrix elements <src|H|tgt> previous = 0.0_dp do i = 1, size(possible_holes) call set_last_tgt(exc, possible_holes(i)) cSum(i) = abs(sltcnd_excit(det_I, canonicalize(exc), .false.)) + previous previous = cSum(i) end do ! Normalize if (near_zero(cSum(size(cSum)))) then cSum(:) = 0.0_dp else cSum(:) = cSum(:) / cSum(size(cSum)) end if end function get_cumulative_list_Excite_2_t !> @brief !> Draw from a cumulative list. subroutine draw_from_cum_list(c_sum, idx, pgen) real(dp), intent(in) :: c_sum(:) integer, intent(out) :: idx real(dp), intent(out) :: pgen character(*), parameter :: this_routine = 'draw_from_cum_list' #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. ((c_sum(size(c_sum)) .isclose. 0.0_dp) .or. (c_sum(size(c_sum)) .isclose. 1.0_dp))) then call stop_all (this_routine, "Assert fail: (c_sum(size(c_sum)) .isclose. 0.0_dp) .or. (c_sum(size(c_sum))& & .isclose. 1.0_dp)") end if end block #endif #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (is_sorted(c_sum))) then call stop_all (this_routine, "Assert fail: is_sorted(c_sum)") end if end block #endif ! there might not be such an excitation if (c_sum(size(c_sum)) > 0) then ! find the index of the target hole in the cumulative list idx = binary_search_first_ge(c_sum, genrand_real2_dSFMT()) #ifdef DEBUG_ block use util_mod, only: stop_all if (.not. (1 <= idx .and. idx <= size(c_sum))) then call stop_all (this_routine, "Assert fail: 1 <= idx .and. idx <= size(c_sum)") end if end block #endif ! adjust pgen with the probability for picking tgt from the cumulative list if (idx == 1) then pgen = c_sum(1) else pgen = c_sum(idx) - c_sum(idx - 1) end if else idx = 0 pgen = 0.0_dp end if end subroutine pure function get_alpha_supergroups(sg, n_orbs, S_z) result(sg_alpha) !! Return the possible supergroups/distributions for alpha electrons. !! !! If `sg_alpha` and `sg_beta` are the distributions of alpha/beta electrons among the !! GAS spaces. Then we have `sg(:) = sg_alpha(:) + sg_beta(:)`. !! We want to generate all possible `sg_alpha` such that !! The GAS constraints are still fullfilled and the total !! spin projection is maintained. (`sum(sg_alpha) + sum(sg_beta) == 2*S_z`) integer, intent(in) :: sg(:) !! The overall supergroup integer, intent(in) :: n_orbs(size(sg)) !! The number of spatial orbitals per GAS space type(SpinProj_t), intent(in) :: S_z !! The Spin projection integer, allocatable :: sg_alpha(:, :) !! All possible distributions of alpha electrons among the !! GAS spaces. integer :: N, i, j N = sum(sg) if (mod(S_z%val + N, 2) == 0) then block type(LocalGASSpec_t) :: sg_alpha_constraint integer :: N_alpha N_alpha = (N + S_z%val) .div. 2 sg_alpha_constraint = LocalGASSpec_t(& n_min=max(sg - n_orbs, 0), & n_max=min(sg, n_orbs), & spat_GAS_orbs=[((j, i = 1, n_orbs(j)), j = 1, size(n_orbs))]) sg_alpha = get_supergroups(sg_alpha_constraint, N_alpha) end block else allocate(sg_alpha(0, 0)) end if end function elemental function get_n_SDs(GAS_spec, N, S_z) result(n_SDs) !! Return the number of Slater-determinants. class(GASSpec_t), intent(in) :: GAS_spec !! GAS specification. integer, intent(in) :: N !! The number of particles type(SpinProj_t), intent(in) :: S_z !! Spin projection integer(int64) :: n_SDs integer, allocatable :: supergroups(:, :), alpha_supergroups(:, :) integer :: i, j, iGAS supergroups = get_supergroups(GAS_spec, N) block integer :: N_alpha(GAS_spec%nGAS()), N_beta(GAS_spec%nGAS()), n_spat_orbs(GAS_spec%nGAS()) !! These are the number of alpha/beta electrons and the number of !! spatial orbitals per GAS space n_spat_orbs = GAS_spec%GAS_size() .div. 2 n_SDs = 0_int64 do j = 1, size(supergroups, 2) alpha_supergroups = get_alpha_supergroups(supergroups(:, j), n_spat_orbs, S_z) do i = 1, size(alpha_supergroups, 2) N_alpha = alpha_supergroups(:, i) N_beta = supergroups(:, j) - N_alpha(:) ! For a given supergroup and S_z in each GAS space ! (coming from the distribution of alpha electrons) ! the number of possible configurations in each GAS space is calculated. ! The overall number is just the product over the GAS spaces. n_SDs = n_SDs + product([(choose_i64(n_spat_orbs(iGAS), N_alpha(iGAS)) & * choose_i64(n_spat_orbs(iGAS), N_beta(iGAS)), iGAS = 1, GAS_spec%nGAS())]) end do end do end block end function subroutine write_GAS_info(GAS_spec, N, S_z, iunit) !! Write info about the GAS constraints to `iunit` !! !! The routine especially compares the CAS and GAS Hilbert space sizes. class(GASSpec_t), intent(in) :: GAS_spec !! GAS constraints. integer, intent(in) :: N !! The particle number. type(SpinProj_t), intent(in) :: S_z !! The total spin projection. integer, intent(in) :: iunit call GAS_spec%write_to(iunit) #ifdef WARNING_WORKAROUND_ associate(N => N); end associate associate(S_z => S_z); end associate #endif if (t_output_GAS_sizes) then block integer :: n_alpha, n_beta, n_spat_orbs integer(int64) :: size_CAS, size_GAS N_alpha = (N + S_z%val) .div. 2 N_beta = N - N_alpha n_spat_orbs = GAS_spec%n_spin_orbs() .div. 2 size_CAS = choose_i64(n_spat_orbs, N_alpha) * choose_i64(n_spat_orbs, N_beta) write(iunit, '(A, 1x, I0)') 'The size of the CAS space is:', size_CAS size_GAS = get_n_SDs(GAS_spec, nEl, S_z) write(iunit, '(A, 1x, I0)') 'The size of the GAS space is:', size_GAS write(iunit, '(A, 1x, E10.5)') 'The fraction of the GAS space is:', real(size_GAS, dp) / real(size_CAS, dp) end block end if end subroutine end module