gasci_on_the_fly_heat_bath.F90 Source File


Contents


Source Code

#include "macros.h"


module gasci_on_the_fly_heat_bath
    use SystemData, only: nEl, AB_elec_pairs, par_elec_pairs
    use constants, only: dp, n_int, maxExcit
    use util_mod, only: binary_search_first_ge, &
        near_zero, operator(.isclose.), custom_findloc
    use sort_mod, only: sort
    use DetBitOps, only: EncodeBitDet
    use sets_mod, only: is_sorted, disjoint
    use bit_rep_data, only: NIfTot
    use dSFMT_interface, only: genrand_real2_dSFMT
    use FciMCData, only: excit_gen_store_type, pParallel
    use get_excit, only: make_double, make_single
    use SymExcitDataMod, only: ScratchSize
    use excit_gens_int_weighted, only: pick_biased_elecs, get_pgen_pick_biased_elecs
    use excitation_types, only: Excitation_t, Excite_1_t, Excite_2_t, &
        get_last_tgt, set_last_tgt, defined, UNKNOWN, &
        excite, ilut_excite, is_canonical
    use orb_idx_mod, only: SpinProj_t, calc_spin_raw, sum
    use util_mod, only: swap

    use excitation_generators, only: &
        SingleExcitationGenerator_t, DoubleExcitationGenerator_t, ExcitationGenerator_t,  &
        gen_exc_sd, gen_all_excits_sd, get_pgen_sd

    use sltcnd_mod, only: sltcnd_excit, dyn_sltcnd_excit

    use gasci, only: GASSpec_t, LocalGASSpec_t
    use gasci_util, only: &
        gen_all_excits, get_cumulative_list, draw_from_cum_list
    better_implicit_none

    private
    public :: GAS_heat_bath_ExcGenerator_t, GAS_singles_heat_bath_ExcGen_t

    !> The heath bath GAS on-the-fly excitation generator
    type, extends(SingleExcitationGenerator_t) :: GAS_singles_heat_bath_ExcGen_t
        private
        class(GASSpec_t), allocatable :: GAS_spec
    contains
        private
        procedure, public :: finalize => GAS_singles_do_nothing

        !> Get the GAS allowed holes for a given determinant and a chosen particle.
        procedure, public :: get_possible_holes => GAS_singles_possible_holes
        procedure, public :: gen_exc => GAS_singles_gen_exc
        procedure, public :: get_pgen => GAS_singles_get_pgen
        procedure, public :: gen_all_excits => GAS_singles_gen_all_excits
    end type

    !> The heath bath GAS on-the-fly excitation generator
    type, extends(DoubleExcitationGenerator_t) :: GAS_doubles_heat_bath_ExcGenerator_t
        private
        class(GASSpec_t), allocatable :: GAS_spec
    contains
        procedure, public :: finalize => GAS_doubles_do_nothing

        !> Get the GAS allowed holes for a given determinant and a chosen particle.
        procedure, public :: get_possible_holes => GAS_doubles_possible_holes
        procedure, public :: gen_exc => GAS_doubles_gen_exc
        procedure, public :: get_pgen => GAS_doubles_get_pgen
        procedure, public :: gen_all_excits => GAS_doubles_gen_all_excits
    end type


    type, extends(ExcitationGenerator_t) :: GAS_heat_bath_ExcGenerator_t
        private
        type(GAS_singles_heat_bath_ExcGen_t) :: singles_generator
        type(GAS_doubles_heat_bath_ExcGenerator_t) :: doubles_generator
    contains
        procedure, public :: finalize => GAS_heat_bath_finalize
        procedure, public :: gen_exc => GAS_heat_bath_gen_exc
        procedure, public :: get_pgen => GAS_heat_bath_get_pgen
        procedure, public :: gen_all_excits => GAS_heat_bath_gen_all_excits
    end type


    interface GAS_heat_bath_ExcGenerator_t
        module procedure construct_GAS_heat_bath_ExcGenerator_t
    end interface


    interface GAS_singles_heat_bath_ExcGen_t
        module procedure construct_GAS_singles_heat_bath_ExcGen_t
    end interface

    interface GAS_doubles_heat_bath_ExcGenerator_t
        module procedure construct_GAS_doubles_heat_bath_ExcGen_t
    end interface

contains

    pure function construct_GAS_heat_bath_ExcGenerator_t(GAS_spec) result(res)
        class(GASSpec_t), intent(in) :: GAS_spec
        type(GAS_heat_bath_ExcGenerator_t) :: res
        res%singles_generator = GAS_singles_heat_bath_ExcGen_t(GAS_spec)
        res%doubles_generator = GAS_doubles_heat_bath_ExcGenerator_t(GAS_spec)
    end function

    pure function construct_GAS_singles_heat_bath_ExcGen_t(GAS_spec) result(res)
        class(GASSpec_t), intent(in) :: GAS_spec
        type(GAS_singles_heat_bath_ExcGen_t) :: res
        res%GAS_spec = GAS_spec
    end function

    pure function construct_GAS_doubles_heat_bath_ExcGen_t(GAS_spec) result(res)
        class(GASSpec_t), intent(in) :: GAS_spec
        type(GAS_doubles_heat_bath_ExcGenerator_t) :: res
        res%GAS_spec = GAS_spec
    end function

    subroutine GAS_singles_do_nothing(this)
        class(GAS_singles_heat_bath_ExcGen_t), intent(inout) :: this
        ! no Initialization => no Finalization
#ifdef WARNING_WORKAROUND_
        associate(this => this); end associate
#endif
    end subroutine

    pure function GAS_singles_possible_holes(this, nI, src) result(unoccupied)
        class(GAS_singles_heat_bath_ExcGen_t), intent(in) :: this
        integer, intent(in) :: nI(:), src
        integer, allocatable :: unoccupied(:)
        unoccupied = this%GAS_spec%get_possible_holes(&
             nI, add_holes=[src], n_total=1, excess=-calc_spin_raw(src))
    end function

    !>  @brief
    !>  Generate a single excitation under GAS constraints.
    subroutine GAS_singles_gen_exc(this, nI, ilutI, nJ, ilutJ, exFlag, ic, &
                                   ex, tParity, pGen, hel, store, part_type)
        class(GAS_singles_heat_bath_ExcGen_t), intent(inout) :: this
        integer, intent(in) :: nI(nEl), exFlag
        integer(n_int), intent(in) :: ilutI(0:NIfTot)
        integer, intent(out) :: nJ(nEl), ic, ex(2, maxExcit)
        integer(n_int), intent(out) :: ilutJ(0:NifTot)
        real(dp), intent(out) :: pGen
        logical, intent(out) :: tParity
        HElement_t(dp), intent(out) :: hel
        type(excit_gen_store_type), intent(inout), target :: store
        integer, intent(in), optional :: part_type
        character(*), parameter :: this_routine = "GAS_singles_gen_exc"

        type(Excite_1_t) :: exc
        integer, allocatable :: possible_holes(:)
        real(dp) :: pgen_particle, pgen_hole
        real(dp), allocatable :: c_sum(:)
        integer :: i, elec

#ifdef WARNING_WORKAROUND_
        associate(store => store); end associate
        associate(exFlag => exFlag); end associate
        associate(part_type => part_type); end associate
#endif
        ic = 1
#ifdef WARNING_WORKAROUND_
        hel = h_cast(0.0_dp)
#endif
        ! Pick any random electron
        elec = int(genrand_real2_dSFMT() * nEl) + 1
        exc%val(1, 1) = nI(elec)
        pgen_particle = 1.0_dp / real(nEl, kind=dp)

        ! Get a hole with the same spin projection
        ! while fullfilling GAS-constraints.
        possible_holes = this%get_possible_holes(nI, exc%val(1, 1))

        if (size(possible_holes) == 0) then
            call zero_result()
            return
        end if

        ! build the cumulative list of matrix elements <src|H|tgt>
        ! with tgt \in possible_holes
        c_sum = get_cumulative_list(nI, exc, possible_holes)
        call draw_from_cum_list(c_sum, i, pgen_hole)
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (i == 0 .neqv. (0.0_dp < pgen_hole .and. pgen_hole <= 1.0_dp))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion i == 0 .neqv. (0.0_dp < pgen_hole .and. pgen_hole <= 1.0_dp)"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:176"
            call stop_all (this_routine, "Assert fail: i == 0 .neqv. (0.0_dp < pgen_hole .and. pgen_hole <= 1.0_dp)")
        end if
    end block
#endif
        if (i /= 0) then
            exc%val(2, 1) = possible_holes(i)
            call make_single(nI, nJ, elec, exc%val(2, 1), ex, tParity)
            ilutJ = ilut_excite(ilutI, exc)
        else
            call zero_result()
        end if

        pgen = pgen_particle * pgen_hole
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (nJ(1) == 0 .neqv. 0.0_dp < pgen .and. pgen <= 1.0_dp)) then
            write(stderr, *) ""
            write(stderr, *) "Assertion nJ(1) == 0 .neqv. 0.0_dp < pgen .and. pgen <= 1.0_dp"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:186"
            call stop_all (this_routine, "Assert fail: nJ(1) == 0 .neqv. 0.0_dp < pgen .and. pgen <= 1.0_dp")
        end if
    end block
#endif
        contains

            subroutine zero_result()
                ex(:, 1) = exc%val(:, 1)
                nJ(1) = 0
                ilutJ = 0_n_int
            end subroutine zero_result
    end subroutine

    function GAS_singles_get_pgen(this, nI, ilutI, ex, ic, ClassCount2, ClassCountUnocc2) result(pgen)
        class(GAS_singles_heat_bath_ExcGen_t), intent(inout) :: this
        integer, intent(in) :: nI(nEl)
        integer(n_int), intent(in) :: ilutI(0:NIfTot)
        integer, intent(in) :: ex(2, maxExcit), ic
        integer, intent(in) :: ClassCount2(ScratchSize), ClassCountUnocc2(ScratchSize)
        real(dp) :: pgen
        character(*), parameter :: this_routine = 'GAS_singles_get_pgen'

        real(dp) :: pgen_particle, pgen_hole
        real(dp), allocatable :: c_sum(:)
        integer, allocatable :: possible_holes(:)
        integer :: idx
#ifdef WARNING_WORKAROUND_
        associate(ClassCount2 => ClassCount2); end associate
        associate(ClassCountUnocc2 => ClassCountUnocc2); end associate
        associate(ilutI => ilutI); end associate
#endif
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (ic == 1)) then
            write(stderr, *) ""
            write(stderr, *) "Assertion ic == 1"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:210"
            call stop_all (this_routine, "Assert fail: ic == 1")
        end if
    end block
#endif

        pgen_particle = 1.0_dp / real(nEl, kind=dp)
        possible_holes = this%get_possible_holes(nI, ex(1, 1))
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (any(ex(2, 1) == possible_holes))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion any(ex(2, 1) == possible_holes)"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:214"
            call stop_all (this_routine, "Assert fail: any(ex(2, 1) == possible_holes)")
        end if
    end block
#endif
        idx = custom_findloc(possible_holes, ex(2, 1))
        ! build the cumulative list of matrix elements <src|H|tgt>
        ! with tgt \in possible_holes
        c_sum = get_cumulative_list(nI, Excite_1_t(src=ex(1, 1)), possible_holes)
        if (idx == 1) then
            pgen_hole = c_sum(1)
        else
            pgen_hole = c_sum(idx) - c_sum(idx - 1)
        end if
        pgen = pgen_particle * pgen_hole
    end function

    subroutine GAS_singles_gen_all_excits(this, nI, n_excits, det_list)
        class(GAS_singles_heat_bath_ExcGen_t), intent(in) :: this
        integer, intent(in) :: nI(nEl)
        integer, intent(out) :: n_excits
        integer(n_int), allocatable, intent(out) :: det_list(:,:)

        call gen_all_excits(this%GAS_spec, nI, n_excits, det_list, ic=1)
    end subroutine


    subroutine GAS_doubles_do_nothing(this)
        class(GAS_doubles_heat_bath_ExcGenerator_t), intent(inout) :: this
        ! no Initialization => no Finalization
#ifdef WARNING_WORKAROUND_
        associate(this => this); end associate
#endif
    end subroutine

    pure function GAS_doubles_possible_holes(this, nI, src, tgt) result(unoccupied)
        class(GAS_doubles_heat_bath_ExcGenerator_t), intent(in) :: this
        integer, intent(in) :: nI(:), src(2)
        integer, intent(in), optional :: tgt
        integer, allocatable :: unoccupied(:)
        type(SpinProj_t) :: excess
        character(*), parameter :: this_routine = "GAS_doubles_possible_holes"

        if (present(tgt)) then
            excess = calc_spin_raw(tgt) - sum(calc_spin_raw(src))
            unoccupied = this%GAS_spec%get_possible_holes(&
                nI, add_holes=src, add_particles=[tgt], n_total=1, excess=excess)
        else
            unoccupied = this%GAS_spec%get_possible_holes(&
                nI, add_holes=src, n_total=2, excess=-sum(calc_spin_raw(src)))
        end if
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        if (.not. (disjoint(unoccupied, nI))) then
            call stop_all (this_routine, "Assert fail: disjoint(unoccupied, nI)")
        end if
    end block
#endif
    end function

    !>  @brief
    !>  Generate a single excitation under GAS constraints.
    subroutine GAS_doubles_gen_exc(this, nI, ilutI, nJ, ilutJ, exFlag, ic, &
                                   ex, tParity, pGen, hel, store, part_type)
        class(GAS_doubles_heat_bath_ExcGenerator_t), intent(inout) :: this
        integer, intent(in) :: nI(nEl), exFlag
        integer(n_int), intent(in) :: ilutI(0:NIfTot)
        integer, intent(out) :: nJ(nEl), ic, ex(2, maxExcit)
        integer(n_int), intent(out) :: ilutJ(0:NifTot)
        real(dp), intent(out) :: pGen
        logical, intent(out) :: tParity
        HElement_t(dp), intent(out) :: hel
        type(excit_gen_store_type), intent(inout), target :: store
        integer, intent(in), optional :: part_type
        character(*), parameter :: this_routine = 'GAS_doubles_gen_exc'

        type(Excite_2_t) :: exc
        integer, allocatable :: possible_holes(:)
        integer :: deleted(2)
        real(dp) :: pgen_particles, &
            ! These are arrays, because the pgen might be different if we
            ! pick AB or BA. Let i, j denote the particles and a, b the holes.
            ! pgen_particles == p (i j)
            ! pgen_first_pick == p(a | i j) == p(b | i j)
            ! pgen_second_pick == [p(b | a i j), p(a | b i j)]
            ! pgen == p_double * p (i j) * p(a | i j) * sum([p(b | a i j), p(a | b i j)])
            !      == p_double * p (i j) * p(b | i j) * sum([p(b | a i j), p(a | b i j)])
            pgen_first_pick, pgen_second_pick(2)
        real(dp) :: r
        real(dp), allocatable :: c_sum(:)
        integer :: i

        integer :: elecs(2), sym_product, ispn, sum_ml

#ifdef WARNING_WORKAROUND_
        associate(exFlag => exFlag); end associate
        associate(store => store); end associate
        associate(part_type => part_type); end associate
#endif
        ic = 2
#ifdef WARNING_WORKAROUND_
        hel = h_cast(0.0_dp)
#endif

        call pick_biased_elecs(nI, elecs, exc%val(1, :), &
                               sym_product, ispn, sum_ml, pgen_particles)
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (exc%val(1, 1) /= exc%val(2, 1))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion exc%val(1, 1) /= exc%val(2, 1)"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:304"
            call stop_all (this_routine, "Assert fail: exc%val(1, 1) /= exc%val(2, 1)")
        end if
    end block
#endif

        deleted = exc%val(1, :)

        ! Get possible holes for the first particle, while fullfilling and spin- and GAS-constraints.
        ! and knowing that a second particle will be created afterwards!
        possible_holes = this%get_possible_holes(nI, deleted)

        if (size(possible_holes) == 0) then
            pgen = pgen_particles
            call zeroResult()
            return
        end if

        r = genrand_real2_dSFMT()
        ! Pick randomly one hole with arbitrary spin
        exc%val(2, 1) = possible_holes(int(r * real(size(possible_holes), kind=dp)) + 1)
        pgen_first_pick = 1.0_dp / real(size(possible_holes), dp)

        ! Pick second hole.
        ! The total spin projection of the created particles has to add up
        ! to the total spin projection of the deleted particles.
        ! Get possible holes for the second particle,
        ! while fullfilling GAS- and Spin-constraints.
        possible_holes = this%get_possible_holes(nI, deleted, exc%val(2, 1))
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (disjoint(possible_holes, exc%val(2:2, 1)))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion disjoint(possible_holes, exc%val(2:2, 1))"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:329"
            call stop_all (this_routine, "Assert fail: disjoint(possible_holes, exc%val(2:2, 1))")
        end if
    end block
#endif

        if (size(possible_holes) == 0) then
            pgen = pgen_particles * pgen_first_pick
            call zeroResult()
            return
        end if

        ! build the cumulative list of matrix elements <src|H|tgt>
        ! with tgt2 in possible_holes
        c_sum = get_cumulative_list(nI, exc, possible_holes)
        call draw_from_cum_list(c_sum, i, pgen_second_pick(1))

        if (i /= 0) then
            exc%val(2, 2) = possible_holes(i)
        else
            pgen = pgen_particles * pgen_first_pick
            call zeroResult()
            return
        end if
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (defined(exc))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion defined(exc)"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:349"
            call stop_all (this_routine, "Assert fail: defined(exc)")
        end if
    end block
#endif

        ! We could have picked the holes the other way round and have to
        ! determine the probability of picking tgt1 with spin m_s_1 upon picking tgt2 first.
        block
            integer :: src1, tgt1, src2, tgt2
            type(Excite_2_t) :: reverted_exc
            src1 = exc%val(1, 1); src2 = exc%val(1, 2);
            tgt1 = exc%val(2, 1); tgt2 = exc%val(2, 2);
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (src1 /= tgt2 .and. src2 /= tgt2)) then
            write(stderr, *) ""
            write(stderr, *) "Assertion src1 /= tgt2 .and. src2 /= tgt2"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:358"
            write(stderr, *) "Values leading to this error:"
            write(stderr, *) "src1 = ", src1
            write(stderr, *) "tgt2 = ", tgt2
            write(stderr, *) "src2 = ", src2
            call stop_all (this_routine, "Assert fail: src1 /= tgt2 .and. src2 /= tgt2")
        end if
    end block
#endif

            possible_holes = this%get_possible_holes(nI, deleted, tgt2)

            if (size(possible_holes) == 0) then
                pgen = pgen_particles * pgen_first_pick * pgen_second_pick(1)
                call zeroResult()
                return
            end if
            ! Possible_holes has to contain tgt1.
            ! we look up its index with binary search
            i = binary_search_first_ge(possible_holes, tgt1)
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (i /= -1)) then
            write(stderr, *) ""
            write(stderr, *) "Assertion i /= -1"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:370"
            write(stderr, *) "Values leading to this error:"
            write(stderr, *) "tgt1 = ", tgt1
            write(stderr, *) "possible_holes = ", possible_holes
            call stop_all (this_routine, "Assert fail: i /= -1")
        end if
    end block
#endif

            reverted_exc = Excite_2_t(src1=src1, tgt1=tgt2, src2=src2)
            c_sum = get_cumulative_list(nI, reverted_exc, possible_holes)
            if (i == 1) then
                pgen_second_pick(2) = c_sum(1)
            else
                pgen_second_pick(2) = (c_sum(i) - c_sum(i - 1))
            end if

            if (i /= 0) then
                call make_double(nI, nJ, elecs(1), elecs(2), tgt1, tgt2, ex, tParity)
                ilutJ = ilut_excite(ilutI, exc)
            else
                ilutJ = 0
            end if
        end block

        pgen = pgen_particles * pgen_first_pick * sum(pgen_second_pick)

#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (0.0_dp < pgen .and. pgen <= 1.0_dp)) then
            write(stderr, *) ""
            write(stderr, *) "Assertion 0.0_dp < pgen .and. pgen <= 1.0_dp"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:390"
            call stop_all (this_routine, "Assert fail: 0.0_dp < pgen .and. pgen <= 1.0_dp")
        end if
    end block
#endif
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (all(ex(:, :2) /= UNKNOWN))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion all(ex(:, :2) /= UNKNOWN)"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:391"
            call stop_all (this_routine, "Assert fail: all(ex(:, :2) /= UNKNOWN)")
        end if
    end block
#endif

        contains

            subroutine zeroResult()
                integer :: src_copy(2)

                src_copy(:) = exc%val(1, :)
                call sort(src_copy)
                ex(1, :2) = src_copy
                ex(2, :2) = exc%val(2, :)
                nJ(1) = 0
                ilutJ = 0_n_int
            end subroutine zeroResult
    end subroutine

    function GAS_doubles_get_pgen(this, nI, ilutI, ex, ic, ClassCount2, ClassCountUnocc2) result(pgen)
        class(GAS_doubles_heat_bath_ExcGenerator_t), intent(inout) :: this
        integer, intent(in) :: nI(nEl)
        integer(n_int), intent(in) :: ilutI(0:NIfTot)
        integer, intent(in) :: ex(2, maxExcit), ic
        integer, intent(in) :: ClassCount2(ScratchSize), ClassCountUnocc2(ScratchSize)
        real(dp) :: pgen
        character(*), parameter :: this_routine = 'GAS_doubles_get_pgen'

        real(dp) :: pgen_particles, &
            ! These are arrays, because the pgen might be different if we
            ! pick AB or BA. Let i, j denote the particles and a, b the holes.
            ! pgen_particles == p (i j)
            ! pgen_first_pick == p(a | i j) == p(b | i j)
            ! pgen_second_pick == [p(b | a i j), p(a | b i j)]
            ! pgen == p_double * p (i j) * p(a | i j) * sum([p(b | a i j), p(a | b i j)])
            !      == p_double * p (i j) * p(b | i j) * sum([p(b | a i j), p(a | b i j)])
            pgen_first_pick, pgen_second_pick(2)
        type(Excite_2_t) :: exc, reverted_exc
        real(dp), allocatable :: c_sum(:)
        integer, allocatable :: possible_holes(:)
        integer :: i
#ifdef WARNING_WORKAROUND_
        associate(ilutI => ilutI); end associate
        associate(ClassCount2 => ClassCount2); end associate
        associate(ClassCountUnocc2 => ClassCountUnocc2); end associate
#endif
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (ic == 2)) then
            write(stderr, *) ""
            write(stderr, *) "Assertion ic == 2"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:430"
            call stop_all (this_routine, "Assert fail: ic == 2")
        end if
    end block
#endif

        exc = Excite_2_t(ex)
#ifdef DEBUG_
    block
        use util_mod, only: stop_all
        use constants, only: stderr
        if (.not. (is_canonical(exc))) then
            write(stderr, *) ""
            write(stderr, *) "Assertion is_canonical(exc)"
            write(stderr, *) "failed in /scratch/jenkins/jobs/existing_branch_doc/workspace/build_config/gfortran-doc/src/gasci_on_&
                &the_fly_heat_bath.fpp:433"
            call stop_all (this_routine, "Assert fail: is_canonical(exc)")
        end if
    end block
#endif

        pgen_particles = get_pgen_pick_biased_elecs(&
                calc_spin_raw(exc%val(1, 1)) == calc_spin_raw(exc%val(1, 2)), &
                pParallel, par_elec_pairs, AB_elec_pairs)

        possible_holes = this%get_possible_holes(nI, exc%val(1, :))
        pgen_first_pick = 1.0_dp / real(size(possible_holes), dp)

        possible_holes = this%get_possible_holes(nI, exc%val(1, :), exc%val(2, 1))
        i = binary_search_first_ge(possible_holes, exc%val(2, 2))
        c_sum = get_cumulative_list(nI, exc, possible_holes)
        if (i == 1) then
            pgen_second_pick(1) = c_sum(1)
        else
            pgen_second_pick(1) = (c_sum(i) - c_sum(i - 1))
        end if

        reverted_exc = exc
        call swap(reverted_exc%val(2, 1), reverted_exc%val(2, 2))

        possible_holes = this%get_possible_holes(nI, reverted_exc%val(1, :), reverted_exc%val(2, 1))
        i = binary_search_first_ge(possible_holes, reverted_exc%val(2, 2))
        c_sum = get_cumulative_list(nI, reverted_exc, possible_holes)
        if (i == 1) then
            pgen_second_pick(2) = c_sum(1)
        else
            pgen_second_pick(2) = (c_sum(i) - c_sum(i - 1))
        end if

        pgen = pgen_particles * pgen_first_pick * sum(pgen_second_pick)
    end function

    subroutine GAS_doubles_gen_all_excits(this, nI, n_excits, det_list)
        class(GAS_doubles_heat_bath_ExcGenerator_t), intent(in) :: this
        integer, intent(in) :: nI(nEl)
        integer, intent(out) :: n_excits
        integer(n_int), allocatable, intent(out) :: det_list(:,:)

        call gen_all_excits(this%GAS_spec, nI, n_excits, det_list, ic=2)
    end subroutine


    subroutine GAS_heat_bath_finalize(this)
        class(GAS_heat_bath_ExcGenerator_t), intent(inout) :: this
        call this%doubles_generator%finalize()
        call this%singles_generator%finalize()
    end subroutine


    subroutine GAS_heat_bath_gen_exc(this, nI, ilutI, nJ, ilutJ, exFlag, ic, &
                                ex, tParity, pGen, hel, store, part_type)
        class(GAS_heat_bath_ExcGenerator_t), intent(inout) :: this
        integer, intent(in) :: nI(nEl), exFlag
        integer(n_int), intent(in) :: ilutI(0:NIfTot)
        integer, intent(out) :: nJ(nEl), ic, ex(2, maxExcit)
        integer(n_int), intent(out) :: ilutJ(0:NifTot)
        real(dp), intent(out) :: pGen
        logical, intent(out) :: tParity
        HElement_t(dp), intent(out) :: hel
        type(excit_gen_store_type), intent(inout), target :: store
        integer, intent(in), optional :: part_type

        call gen_exc_sd(nI, ilutI, nJ, ilutJ, exFlag, ic, &
                        ex, tParity, pGen, hel, store, part_type, &
                        this%singles_generator, this%doubles_generator)
    end subroutine


    real(dp) function GAS_heat_bath_get_pgen(&
            this, nI, ilutI, ex, ic, ClassCount2, ClassCountUnocc2)
        class(GAS_heat_bath_ExcGenerator_t), intent(inout) :: this
        integer, intent(in) :: nI(nEl)
        integer(n_int), intent(in) :: ilutI(0:NIfTot)
        integer, intent(in) :: ex(2, maxExcit), ic
        integer, intent(in) :: ClassCount2(ScratchSize), ClassCountUnocc2(ScratchSize)
        GAS_heat_bath_get_pgen = get_pgen_sd(&
                        nI, ilutI, ex, ic, ClassCount2, ClassCountUnocc2, &
                        this%singles_generator, this%doubles_generator)
    end function


    subroutine GAS_heat_bath_gen_all_excits(this, nI, n_excits, det_list)
        class(GAS_heat_bath_ExcGenerator_t), intent(in) :: this
        integer, intent(in) :: nI(nEl)
        integer, intent(out) :: n_excits
        integer(n_int), allocatable, intent(out) :: det_list(:,:)
        call gen_all_excits_sd(nI, n_excits, det_list, &
                               this%singles_generator, this%doubles_generator)
    end subroutine


end module gasci_on_the_fly_heat_bath