LCOV - code coverage report
Current view: top level - src - bse_util.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:123285f) Lines: 81.6 % 484 395
Test Date: 2026-11-04 00:58:17 Functions: 95.0 % 20 19

            Line data    Source code
       1              : !--------------------------------------------------------------------------------------------------!
       2              : !   CP2K: A general program to perform molecular dynamics simulations                              !
       3              : !   Copyright 2000-2026 CP2K developers group <https://cp2k.org>                                   !
       4              : !                                                                                                  !
       5              : !   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
       6              : !--------------------------------------------------------------------------------------------------!
       7              : 
       8              : ! **************************************************************************************************
       9              : !> \brief Auxiliary routines for GW + Bethe-Salpeter for computing electronic excitations
      10              : !> \par History
      11              : !>      11.2023 created [Maximilian Graml]
      12              : ! **************************************************************************************************
      13              : MODULE bse_util
      14              :    USE atomic_kind_types,               ONLY: atomic_kind_type
      15              :    USE bse_types,                       ONLY: bse_env_type
      16              :    USE cell_types,                      ONLY: cell_type
      17              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_type
      18              :    USE cp_control_types,                ONLY: dft_control_type
      19              :    USE cp_dbcsr_api,                    ONLY: dbcsr_p_type
      20              :    USE cp_dbcsr_operations,             ONLY: cp_dbcsr_sm_fm_multiply
      21              :    USE cp_fm_basic_linalg,              ONLY: cp_fm_trace
      22              :    USE cp_fm_redistribution,            ONLY: cp_fm_permute
      23              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      24              :                                               cp_fm_struct_release,&
      25              :                                               cp_fm_struct_type
      26              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      27              :                                               cp_fm_get_info,&
      28              :                                               cp_fm_release,&
      29              :                                               cp_fm_set_all,&
      30              :                                               cp_fm_to_fm_submat,&
      31              :                                               cp_fm_to_fm_submat_general,&
      32              :                                               cp_fm_type
      33              :    USE cp_log_handling,                 ONLY: cp_get_default_logger,&
      34              :                                               cp_logger_type
      35              :    USE cp_output_handling,              ONLY: cp_print_key_finished_output,&
      36              :                                               cp_print_key_unit_nr
      37              :    USE cp_realspace_grid_cube,          ONLY: cp_pw_to_cube
      38              :    USE input_constants,                 ONLY: bse_iterdiag,&
      39              :                                               bse_screening_alpha,&
      40              :                                               bse_screening_rpa,&
      41              :                                               bse_screening_tdhf
      42              :    USE input_section_types,             ONLY: section_vals_type,&
      43              :                                               section_vals_val_get
      44              :    USE kinds,                           ONLY: default_path_length,&
      45              :                                               dp,&
      46              :                                               int_8
      47              :    USE message_passing,                 ONLY: mp_para_env_type
      48              :    USE mo_window,                       ONLY: combine_mo_windows,&
      49              :                                               determine_mo_window,&
      50              :                                               mo_window_type
      51              :    USE parallel_gemm_api,               ONLY: parallel_gemm
      52              :    USE particle_list_types,             ONLY: particle_list_type
      53              :    USE particle_types,                  ONLY: particle_type
      54              :    USE physcon,                         ONLY: evolt
      55              :    USE pw_env_types,                    ONLY: pw_env_get,&
      56              :                                               pw_env_type
      57              :    USE pw_pool_types,                   ONLY: pw_pool_p_type,&
      58              :                                               pw_pool_type
      59              :    USE pw_types,                        ONLY: pw_c1d_gs_type,&
      60              :                                               pw_r3d_rs_type
      61              :    USE qs_collocate_density,            ONLY: calculate_wavefunction
      62              :    USE qs_kind_types,                   ONLY: qs_kind_type
      63              :    USE qs_mo_types,                     ONLY: get_mo_set,&
      64              :                                               mo_set_type
      65              :    USE qs_subsys_types,                 ONLY: qs_subsys_get,&
      66              :                                               qs_subsys_type
      67              :    USE util,                            ONLY: sort,&
      68              :                                               sort_unique
      69              : #include "./base/base_uses.f90"
      70              : 
      71              :    IMPLICIT NONE
      72              : 
      73              :    PRIVATE
      74              : 
      75              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'bse_util'
      76              : 
      77              :    PUBLIC :: screen_slabs, bse_window_from_dft, bse_window_from_mos, &
      78              :              comp_eigvec_coeff_BSE, sort_excitations, &
      79              :              estimate_BSE_resources, fulldiag_memory_estimate_GB, filter_eigvec_contrib, truncate_BSE_matrices, &
      80              :              adapt_BSE_input_params, adapt_BSE_screening, get_multipoles_mo, &
      81              :              reshuffle_eigvec, print_bse_nto_cubes, trace_exciton_descr, &
      82              :              get_bse_spin_block_layout, assemble_joint_ov_slab, &
      83              :              ia_of_occ_virt, occ_of_ia, virt_of_ia
      84              : 
      85              : CONTAINS
      86              : 
      87              : ! **************************************************************************************************
      88              : !> \brief Compound transition index ia = (i-1)*virt + a of the pair (i, a): a is the fast index
      89              : !> \param i_occ occupied level
      90              : !> \param a_virt virtual level
      91              : !> \param virt number of virtual levels
      92              : !> \return ia
      93              : ! **************************************************************************************************
      94        41664 :    PURE FUNCTION ia_of_occ_virt(i_occ, a_virt, virt) RESULT(ia)
      95              : 
      96              :       INTEGER, INTENT(IN)                                :: i_occ, a_virt, virt
      97              :       INTEGER                                            :: ia
      98              : 
      99        41664 :       ia = (i_occ - 1)*virt + a_virt
     100              : 
     101        41664 :    END FUNCTION ia_of_occ_virt
     102              : 
     103              : ! **************************************************************************************************
     104              : !> \brief Occupied level of the compound transition index ia = (i-1)*virt + a
     105              : !> \param ia compound transition index
     106              : !> \param virt number of virtual levels
     107              : !> \return i
     108              : ! **************************************************************************************************
     109        18175 :    PURE FUNCTION occ_of_ia(ia, virt) RESULT(i_occ)
     110              : 
     111              :       INTEGER, INTENT(IN)                                :: ia, virt
     112              :       INTEGER                                            :: i_occ
     113              : 
     114        18175 :       i_occ = (ia - 1)/virt + 1
     115              : 
     116        18175 :    END FUNCTION occ_of_ia
     117              : 
     118              : ! **************************************************************************************************
     119              : !> \brief Virtual level of the compound transition index ia = (i-1)*virt + a
     120              : !> \param ia compound transition index
     121              : !> \param virt number of virtual levels
     122              : !> \return a
     123              : ! **************************************************************************************************
     124        18175 :    PURE FUNCTION virt_of_ia(ia, virt) RESULT(a_virt)
     125              : 
     126              :       INTEGER, INTENT(IN)                                :: ia, virt
     127              :       INTEGER                                            :: a_virt
     128              : 
     129        18175 :       a_virt = MOD(ia - 1, virt) + 1
     130              : 
     131        18175 :    END FUNCTION virt_of_ia
     132              : 
     133              : ! **************************************************************************************************
     134              : !> \brief The screened slabs \bar{B}^P_ij = sum_Q [1+Q(0)]^-1_PQ B^Q_ij and
     135              : !>        \bar{B}^P_ia = sum_Q [1+Q(0)]^-1_PQ B^Q_ia, P, Q ≤ dimen_RI_red
     136              : !> \param fm_eps_inv [1+Q(0)]^-1_PQ
     137              : !> \param fm_S_ij B^P_ij of the window, dimen_RI x homo^2
     138              : !> \param fm_S_ia B^P_ia of the window, dimen_RI x homo*virtual
     139              : !> \param dimen_RI_red reduced RI dimension, the contraction length
     140              : !> \param homo occupied levels in the window
     141              : !> \param virtual virtual levels in the window
     142              : !> \param fm_S_bar_ij \bar{B}^P_ij, created here on fm_S_ij's structure
     143              : !> \param fm_S_bar_ia \bar{B}^P_ia, created here on fm_S_ia's structure for ABBA only
     144              : !> \param do_abba whether the coupling matrix B needs the screened ia slab
     145              : ! **************************************************************************************************
     146          140 :    SUBROUTINE screen_slabs(fm_eps_inv, fm_S_ij, fm_S_ia, dimen_RI_red, homo, virtual, &
     147              :                            fm_S_bar_ij, fm_S_bar_ia, do_abba)
     148              : 
     149              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_eps_inv, fm_S_ij, fm_S_ia
     150              :       INTEGER, INTENT(IN)                                :: dimen_RI_red, homo, virtual
     151              :       TYPE(cp_fm_type), INTENT(OUT)                      :: fm_S_bar_ij, fm_S_bar_ia
     152              :       LOGICAL, INTENT(IN)                                :: do_abba
     153              : 
     154              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'screen_slabs'
     155              : 
     156              :       INTEGER                                            :: handle
     157              : 
     158           70 :       CALL timeset(routineN, handle)
     159              : 
     160           70 :       CALL cp_fm_create(fm_S_bar_ij, fm_S_ij%matrix_struct)
     161           70 :       CALL cp_fm_set_all(fm_S_bar_ij, 0.0_dp)
     162              : 
     163              :       ! \bar{B}^P_ij = sum_Q [1+Q(0)]^-1_PQ B^Q_ij
     164              :       CALL parallel_gemm(transa="N", transb="N", m=dimen_RI_red, n=homo**2, k=dimen_RI_red, alpha=1.0_dp, &
     165           70 :                          matrix_a=fm_eps_inv, matrix_b=fm_S_ij, beta=0.0_dp, matrix_c=fm_S_bar_ij)
     166              : 
     167           70 :       IF (do_abba) THEN
     168           28 :          CALL cp_fm_create(fm_S_bar_ia, fm_S_ia%matrix_struct)
     169           28 :          CALL cp_fm_set_all(fm_S_bar_ia, 0.0_dp)
     170              :          ! \bar{B}^P_ia = sum_Q [1+Q(0)]^-1_PQ B^Q_ia; fm_S_ia comes from RPA and has a different
     171              :          ! blacs_env than fm_S_ij
     172              :          CALL parallel_gemm(transa="N", transb="N", m=dimen_RI_red, n=homo*virtual, k=dimen_RI_red, alpha=1.0_dp, &
     173           28 :                             matrix_a=fm_eps_inv, matrix_b=fm_S_ia, beta=0.0_dp, matrix_c=fm_S_bar_ia)
     174              :       END IF
     175              : 
     176           70 :       CALL timestop(handle)
     177              : 
     178           70 :    END SUBROUTINE screen_slabs
     179              : 
     180              : ! **************************************************************************************************
     181              : !> \brief Routine for computing the coefficients of the eigenvectors of the BSE matrix from a
     182              : !>  multiplication with the eigenvalues
     183              : !> \param fm_work ...
     184              : !> \param eig_vals ...
     185              : !> \param beta ...
     186              : !> \param gamma ...
     187              : !> \param do_transpose ...
     188              : ! **************************************************************************************************
     189          104 :    SUBROUTINE comp_eigvec_coeff_BSE(fm_work, eig_vals, beta, gamma, do_transpose)
     190              : 
     191              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_work
     192              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     193              :          INTENT(IN)                                      :: eig_vals
     194              :       REAL(KIND=dp), INTENT(IN)                          :: beta
     195              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: gamma
     196              :       LOGICAL, INTENT(IN), OPTIONAL                      :: do_transpose
     197              : 
     198              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'comp_eigvec_coeff_BSE'
     199              : 
     200              :       INTEGER                                            :: handle, i_row_global, ii, j_col_global, &
     201              :                                                             jj, ncol_local, nrow_local
     202           52 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
     203              :       LOGICAL                                            :: my_do_transpose
     204              :       REAL(KIND=dp)                                      :: coeff, my_gamma
     205              : 
     206           52 :       CALL timeset(routineN, handle)
     207              : 
     208           52 :       IF (PRESENT(gamma)) THEN
     209           52 :          my_gamma = gamma
     210              :       ELSE
     211              :          my_gamma = 2.0_dp
     212              :       END IF
     213              : 
     214           52 :       IF (PRESENT(do_transpose)) THEN
     215           52 :          my_do_transpose = do_transpose
     216              :       ELSE
     217              :          my_do_transpose = .FALSE.
     218              :       END IF
     219              : 
     220              :       CALL cp_fm_get_info(matrix=fm_work, &
     221              :                           nrow_local=nrow_local, &
     222              :                           ncol_local=ncol_local, &
     223              :                           row_indices=row_indices, &
     224           52 :                           col_indices=col_indices)
     225              : 
     226           52 :       IF (my_do_transpose) THEN
     227         4080 :          DO jj = 1, ncol_local
     228         4028 :             j_col_global = col_indices(jj)
     229      1548600 :             DO ii = 1, nrow_local
     230      1544520 :                coeff = (eig_vals(j_col_global)**beta)/my_gamma
     231      1548548 :                fm_work%local_data(ii, jj) = fm_work%local_data(ii, jj)*coeff
     232              :             END DO
     233              :          END DO
     234              :       ELSE
     235            0 :          DO jj = 1, ncol_local
     236            0 :             DO ii = 1, nrow_local
     237            0 :                i_row_global = row_indices(ii)
     238            0 :                coeff = (eig_vals(i_row_global)**beta)/my_gamma
     239            0 :                fm_work%local_data(ii, jj) = fm_work%local_data(ii, jj)*coeff
     240              :             END DO
     241              :          END DO
     242              :       END IF
     243              : 
     244           52 :       CALL timestop(handle)
     245              : 
     246           52 :    END SUBROUTINE comp_eigvec_coeff_BSE
     247              : 
     248              : ! **************************************************************************************************
     249              : !> \brief Sorts excitation entries by ascending primary index, reordering the secondary index,
     250              : !>        the eigenvector coefficients and - open shell - the spin index alongside
     251              : !> \param idx_prim Primary index of each entry; sorted in place and used as the sort key
     252              : !> \param idx_sec Secondary index of each entry, reordered to follow idx_prim
     253              : !> \param eigvec_entries Eigenvector coefficients of each entry, reordered to follow idx_prim
     254              : !> \param idx_spin Optional spin index of each entry (open shell), reordered to follow idx_prim
     255              : ! **************************************************************************************************
     256         1634 :    SUBROUTINE sort_excitations(idx_prim, idx_sec, eigvec_entries, idx_spin)
     257              : 
     258              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: idx_prim, idx_sec
     259              :       REAL(kind=dp), ALLOCATABLE, DIMENSION(:)           :: eigvec_entries
     260              :       INTEGER, ALLOCATABLE, DIMENSION(:), OPTIONAL       :: idx_spin
     261              : 
     262              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'sort_excitations'
     263              : 
     264              :       INTEGER                                            :: handle, ii, kk, num_entries, num_mults
     265         1634 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: idx_prim_work, idx_sec_work, &
     266         1634 :                                                             idx_spin_work, tmp_index
     267              :       LOGICAL                                            :: unique_entries
     268         1634 :       REAL(kind=dp), ALLOCATABLE, DIMENSION(:)           :: eigvec_entries_work
     269              : 
     270         1634 :       CALL timeset(routineN, handle)
     271              : 
     272         1634 :       num_entries = SIZE(idx_prim)
     273              : 
     274         4326 :       ALLOCATE (tmp_index(num_entries))
     275              : 
     276         1634 :       CALL sort(idx_prim, num_entries, tmp_index)
     277              : 
     278         2692 :       ALLOCATE (idx_sec_work(num_entries))
     279         4326 :       ALLOCATE (eigvec_entries_work(num_entries))
     280         1830 :       IF (PRESENT(idx_spin)) ALLOCATE (idx_spin_work(num_entries))
     281              : 
     282         4664 :       DO ii = 1, num_entries
     283         3030 :          idx_sec_work(ii) = idx_sec(tmp_index(ii))
     284         3030 :          eigvec_entries_work(ii) = eigvec_entries(tmp_index(ii))
     285         4664 :          IF (PRESENT(idx_spin)) idx_spin_work(ii) = idx_spin(tmp_index(ii))
     286              :       END DO
     287              : 
     288         1634 :       DEALLOCATE (tmp_index)
     289         1634 :       DEALLOCATE (idx_sec)
     290         1634 :       DEALLOCATE (eigvec_entries)
     291              : 
     292         1634 :       CALL MOVE_ALLOC(idx_sec_work, idx_sec)
     293         1634 :       CALL MOVE_ALLOC(eigvec_entries_work, eigvec_entries)
     294         1634 :       IF (PRESENT(idx_spin)) THEN
     295          250 :          DEALLOCATE (idx_spin)
     296          250 :          CALL MOVE_ALLOC(idx_spin_work, idx_spin)
     297              :       END IF
     298              : 
     299              :       !Now check for multiple entries in first idx to check necessity of sorting in second idx
     300         1634 :       CALL sort_unique(idx_prim, unique_entries)
     301         1634 :       IF (.NOT. unique_entries) THEN
     302          928 :          ALLOCATE (idx_prim_work(num_entries))
     303         2474 :          idx_prim_work(:) = idx_prim(:)
     304              :          ! Find duplicate entries in idx_prim
     305         2474 :          DO ii = 1, num_entries
     306         2010 :             IF (idx_prim_work(ii) == 0) CYCLE
     307         7180 :             num_mults = COUNT(idx_prim_work == idx_prim_work(ii))
     308         1178 :             IF (num_mults > 1) THEN
     309              :                !Set all duplicate entries to 0
     310         2136 :                idx_prim_work(ii:ii + num_mults - 1) = 0
     311              :                !Start sorting in secondary index
     312         1956 :                ALLOCATE (idx_sec_work(num_mults))
     313         1956 :                ALLOCATE (eigvec_entries_work(num_mults))
     314         2136 :                idx_sec_work(:) = idx_sec(ii:ii + num_mults - 1)
     315         2136 :                eigvec_entries_work(:) = eigvec_entries(ii:ii + num_mults - 1)
     316          652 :                IF (PRESENT(idx_spin)) THEN
     317          400 :                   ALLOCATE (idx_spin_work(num_mults))
     318          666 :                   idx_spin_work(:) = idx_spin(ii:ii + num_mults - 1)
     319              :                END IF
     320         1304 :                ALLOCATE (tmp_index(num_mults))
     321          652 :                CALL sort(idx_sec_work, num_mults, tmp_index)
     322              : 
     323              :                !Now write newly sorted indices to original arrays
     324         2136 :                DO kk = ii, ii + num_mults - 1
     325         1484 :                   idx_sec(kk) = idx_sec_work(kk - ii + 1)
     326         1484 :                   eigvec_entries(kk) = eigvec_entries_work(tmp_index(kk - ii + 1))
     327         2136 :                   IF (PRESENT(idx_spin)) idx_spin(kk) = idx_spin_work(tmp_index(kk - ii + 1))
     328              :                END DO
     329              :                !Deallocate work arrays
     330          652 :                DEALLOCATE (tmp_index)
     331          652 :                DEALLOCATE (idx_sec_work)
     332          652 :                DEALLOCATE (eigvec_entries_work)
     333          652 :                IF (PRESENT(idx_spin)) DEALLOCATE (idx_spin_work)
     334              :             END IF
     335         2474 :             idx_prim_work(ii) = idx_prim(ii)
     336              :          END DO
     337          464 :          DEALLOCATE (idx_prim_work)
     338              :       END IF
     339              : 
     340         1634 :       CALL timestop(handle)
     341              : 
     342         4902 :    END SUBROUTINE sort_excitations
     343              : 
     344              : ! **************************************************************************************************
     345              : !> \brief Roughly estimates the needed runtime and memory during the BSE run
     346              : !> \param n_ov_joint ...
     347              : !> \param unit_nr ...
     348              : !> \param bse_abba ...
     349              : !> \param para_env ...
     350              : !> \param diag_runtime_est ...
     351              : ! **************************************************************************************************
     352           54 :    SUBROUTINE estimate_BSE_resources(n_ov_joint, unit_nr, bse_abba, &
     353              :                                      para_env, diag_runtime_est)
     354              : 
     355              :       INTEGER, INTENT(IN)                                :: n_ov_joint, unit_nr
     356              :       LOGICAL                                            :: bse_abba
     357              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     358              :       REAL(KIND=dp)                                      :: diag_runtime_est
     359              : 
     360              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'estimate_BSE_resources'
     361              : 
     362              :       INTEGER                                            :: handle
     363              :       REAL(KIND=dp)                                      :: mem_est, mem_est_per_rank
     364              : 
     365           54 :       CALL timeset(routineN, handle)
     366              : 
     367           54 :       mem_est = fulldiag_memory_estimate_GB(n_ov_joint, bse_abba)
     368           54 :       mem_est_per_rank = REAL(mem_est/para_env%num_pe, KIND=dp)
     369              : 
     370           54 :       IF (unit_nr > 0) THEN
     371              :          ! WRITE (unit_nr, '(T2,A4,T7,A40,T68,F13.3)') 'BSE|', 'Total peak memory estimate from BSE [GB]', &
     372              :          !    mem_est
     373           27 :          WRITE (unit_nr, '(T2,A4,T7,A40,T68,ES13.3)') 'BSE|', 'Total peak memory estimate from BSE [GB]', &
     374           54 :             mem_est
     375           27 :          WRITE (unit_nr, '(T2,A4,T7,A47,T68,F13.3)') 'BSE|', 'Peak memory estimate per MPI rank from BSE [GB]', &
     376           54 :             mem_est_per_rank
     377           27 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     378              :       END IF
     379              :       ! Rough estimation of diagonalization runtimes. Baseline was a full BSE Naphthalene
     380              :       ! run with 11000x11000 entries in A/B/C, which took 10s on 32 ranks
     381              :       diag_runtime_est = REAL(INT(n_ov_joint, KIND=int_8)/11000_int_8, KIND=dp)**3* &
     382           54 :                          10*32/REAL(para_env%num_pe, KIND=dp)
     383              : 
     384           54 :       CALL timestop(handle)
     385              : 
     386           54 :    END SUBROUTINE estimate_BSE_resources
     387              : 
     388              : ! **************************************************************************************************
     389              : !> \brief Peak memory of the full diagonalization in GB, all ranks together: n_ov^2 doubles times
     390              : !>        the matrices held at once, 2 for TDA (A and W_ijab), 10 for ABBA (B, C and the six that
     391              : !>        build C added)
     392              : !> \param n_ov_joint transition pairs in the active window, summed over spins
     393              : !> \param do_abba full problem, not TDA
     394              : !> \return the estimate in GB
     395              : ! **************************************************************************************************
     396          108 :    PURE FUNCTION fulldiag_memory_estimate_GB(n_ov_joint, do_abba) RESULT(mem_GB)
     397              : 
     398              :       INTEGER, INTENT(IN)                                :: n_ov_joint
     399              :       LOGICAL, INTENT(IN)                                :: do_abba
     400              :       REAL(KIND=dp)                                      :: mem_GB
     401              : 
     402              :       INTEGER                                            :: num_BSE_matrices
     403              :       INTEGER(KIND=int_8)                                :: full_dim
     404              : 
     405          108 :       num_BSE_matrices = 2
     406          108 :       IF (do_abba) num_BSE_matrices = 10
     407          108 :       full_dim = INT(n_ov_joint, KIND=int_8)**2*INT(num_BSE_matrices, KIND=int_8)
     408              :       ! 1 GB = 1E9 B, the unit of the free-memory budget it is compared with
     409          108 :       mem_GB = 8.0E-9_dp*REAL(full_dim, KIND=dp)
     410              : 
     411          108 :    END FUNCTION fulldiag_memory_estimate_GB
     412              : 
     413              : ! **************************************************************************************************
     414              : !> \brief Filters eigenvector entries above a given threshold to describe excitations in the
     415              : !> singleparticle basis
     416              : !> \param fm_eigvec ...
     417              : !> \param idx_homo ...
     418              : !> \param idx_virt ...
     419              : !> \param eigvec_entries ...
     420              : !> \param i_exc ...
     421              : !> \param virtual ...
     422              : !> \param num_entries ...
     423              : !> \param bse_env the BSE environment (settings)
     424              : !> \param offsets ...
     425              : !> \param virtual_per_spin ...
     426              : !> \param idx_spin ...
     427              : ! **************************************************************************************************
     428         1634 :    SUBROUTINE filter_eigvec_contrib(fm_eigvec, idx_homo, idx_virt, eigvec_entries, &
     429              :                                     i_exc, virtual, num_entries, bse_env, &
     430         1634 :                                     offsets, virtual_per_spin, idx_spin)
     431              : 
     432              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_eigvec
     433              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: idx_homo, idx_virt
     434              :       REAL(kind=dp), ALLOCATABLE, DIMENSION(:)           :: eigvec_entries
     435              :       INTEGER                                            :: i_exc, virtual, num_entries
     436              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
     437              :       INTEGER, DIMENSION(:), INTENT(IN), OPTIONAL        :: offsets, virtual_per_spin
     438              :       INTEGER, ALLOCATABLE, DIMENSION(:), OPTIONAL       :: idx_spin
     439              : 
     440              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'filter_eigvec_contrib'
     441              : 
     442              :       INTEGER                                            :: eigvec_idx, handle, ii, iproc, isp, jj, &
     443              :                                                             kk, ksp, ncol_local, nrow_local, &
     444              :                                                             num_entries_local, r_local, v_local
     445         1634 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: displ, indx_all, indx_local, &
     446         1634 :                                                             num_entries_to_comm
     447         1634 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
     448              :       REAL(KIND=dp)                                      :: eigvec_entry
     449              :       REAL(kind=dp), ALLOCATABLE, DIMENSION(:)           :: entries_local
     450              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     451              : 
     452         1634 :       CALL timeset(routineN, handle)
     453              : 
     454         1634 :       para_env => fm_eigvec%matrix_struct%para_env
     455              : 
     456              :       CALL cp_fm_get_info(matrix=fm_eigvec, &
     457              :                           nrow_local=nrow_local, &
     458              :                           ncol_local=ncol_local, &
     459              :                           row_indices=row_indices, &
     460         1634 :                           col_indices=col_indices)
     461              : 
     462              :       ! the local entries of column i_exc above EPS_X: count, then collect with their (i, a, sigma)
     463         1634 :       num_entries_local = 0
     464       104460 :       DO jj = 1, ncol_local
     465       102826 :          IF (col_indices(jj) /= i_exc) CYCLE
     466        70140 :          DO ii = 1, nrow_local
     467       169738 :             IF (ABS(fm_eigvec%local_data(ii, jj)) > bse_env%eps_x) num_entries_local = num_entries_local + 1
     468              :          END DO
     469              :       END DO
     470         6606 :       ALLOCATE (entries_local(num_entries_local), indx_local(3*num_entries_local))
     471         1634 :       kk = 0
     472       104460 :       DO jj = 1, ncol_local
     473       102826 :          IF (col_indices(jj) /= i_exc) CYCLE
     474        70140 :          DO ii = 1, nrow_local
     475        66912 :             eigvec_idx = row_indices(ii)
     476        66912 :             eigvec_entry = fm_eigvec%local_data(ii, jj)
     477       169738 :             IF (ABS(eigvec_entry) > bse_env%eps_x) THEN
     478              :                ! Decode spin block from the joint row index (blocks are contiguous; sigma is the
     479              :                ! largest offset strictly below eigvec_idx). offsets absent -> closed shell, sigma=1.
     480         1515 :                isp = 1
     481         1515 :                r_local = eigvec_idx
     482         1515 :                v_local = virtual
     483         1515 :                IF (PRESENT(offsets)) THEN
     484          417 :                   DO ksp = SIZE(offsets), 1, -1
     485          417 :                      IF (eigvec_idx > offsets(ksp)) THEN
     486              :                         isp = ksp
     487              :                         EXIT
     488              :                      END IF
     489              :                   END DO
     490          286 :                   r_local = eigvec_idx - offsets(isp)
     491          286 :                   v_local = virtual_per_spin(isp)
     492              :                END IF
     493         1515 :                kk = kk + 1
     494         1515 :                indx_local(3*kk - 2) = occ_of_ia(r_local, v_local)
     495         1515 :                indx_local(3*kk - 1) = virt_of_ia(r_local, v_local)
     496         1515 :                indx_local(3*kk) = isp
     497         1515 :                entries_local(kk) = eigvec_entry
     498              :             END IF
     499              :          END DO
     500              :       END DO
     501              : 
     502              :       ! one allgather of the counts, one allgatherv per array: every rank ends with the same
     503              :       ! rank-ordered list
     504         6536 :       ALLOCATE (num_entries_to_comm(0:para_env%num_pe - 1), displ(0:para_env%num_pe - 1))
     505         1634 :       CALL para_env%allgather(num_entries_local, num_entries_to_comm)
     506         1634 :       displ(0) = 0
     507         3268 :       DO iproc = 1, para_env%num_pe - 1
     508         3268 :          displ(iproc) = displ(iproc - 1) + num_entries_to_comm(iproc - 1)
     509              :       END DO
     510         4902 :       num_entries = SUM(num_entries_to_comm)
     511         7018 :       ALLOCATE (eigvec_entries(num_entries), indx_all(3*num_entries))
     512         1634 :       CALL para_env%allgatherv(entries_local, eigvec_entries, num_entries_to_comm, displ)
     513         8170 :       CALL para_env%allgatherv(indx_local, indx_all, 3*num_entries_to_comm, 3*displ)
     514              : 
     515         5384 :       ALLOCATE (idx_homo(num_entries), idx_virt(num_entries))
     516         1830 :       IF (PRESENT(idx_spin)) ALLOCATE (idx_spin(num_entries))
     517         4664 :       DO kk = 1, num_entries
     518         3030 :          idx_homo(kk) = indx_all(3*kk - 2)
     519         3030 :          idx_virt(kk) = indx_all(3*kk - 1)
     520         4664 :          IF (PRESENT(idx_spin)) idx_spin(kk) = indx_all(3*kk)
     521              :       END DO
     522              : 
     523         1634 :       DEALLOCATE (entries_local, indx_local, indx_all, num_entries_to_comm, displ)
     524         1634 :       NULLIFY (row_indices)
     525         1634 :       NULLIFY (col_indices)
     526              : 
     527              :       !Now sort the results according to the involved singleparticle orbitals
     528              :       ! (homo first, then virtual). idx_spin is payload, permuted alongside the entries.
     529         1634 :       IF (PRESENT(idx_spin)) THEN
     530          250 :          CALL sort_excitations(idx_homo, idx_virt, eigvec_entries, idx_spin)
     531              :       ELSE
     532         1384 :          CALL sort_excitations(idx_homo, idx_virt, eigvec_entries)
     533              :       END IF
     534              : 
     535         1634 :       CALL timestop(handle)
     536              : 
     537         1634 :    END SUBROUTINE filter_eigvec_contrib
     538              : 
     539              : ! **************************************************************************************************
     540              : !> \brief Spin-block layout for the open-shell (joint) BSE matrix: per-spin OV-pair counts and the
     541              : !>        block offsets into the joint matrix of dimension n_ov_joint = sum_sigma homo*virtual.
     542              : !> \param homo_red per-spin (reduced) number of occupied levels
     543              : !> \param virt_red per-spin (reduced) number of virtual levels
     544              : !> \param n_ov per-spin OV-pair count (OUT)
     545              : !> \param offsets per-spin block offset into the joint matrix (OUT)
     546              : !> \param n_ov_joint total joint dimension (OUT)
     547              : ! **************************************************************************************************
     548          222 :    SUBROUTINE get_bse_spin_block_layout(homo_red, virt_red, n_ov, offsets, n_ov_joint)
     549              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: homo_red, virt_red
     550              :       INTEGER, DIMENSION(:), INTENT(OUT)                 :: n_ov, offsets
     551              :       INTEGER, INTENT(OUT)                               :: n_ov_joint
     552              : 
     553              :       INTEGER                                            :: isp
     554              : 
     555          222 :       n_ov_joint = 0
     556          504 :       DO isp = 1, SIZE(homo_red)
     557          282 :          offsets(isp) = n_ov_joint
     558          282 :          n_ov(isp) = homo_red(isp)*virt_red(isp)
     559          504 :          n_ov_joint = n_ov_joint + n_ov(isp)
     560              :       END DO
     561              : 
     562          222 :    END SUBROUTINE get_bse_spin_block_layout
     563              : 
     564              : ! **************************************************************************************************
     565              : !> \brief The active MO window on the DFT axis: one absolute MO range for every spin; when the spin
     566              : !>        windows differ, the smallest range covering all of them
     567              : !> \param eigenval_scf DFT eigenvalues (level, spin)
     568              : !> \param n_mo number of MOs per spin
     569              : !> \param homo occupied MOs per spin
     570              : !> \param cutoff_occ ENERGY_CUTOFF_OCC in Hartree, zero or negative for no cutoff
     571              : !> \param cutoff_empty ENERGY_CUTOFF_EMPTY in Hartree, zero or negative for no cutoff
     572              : !> \param window first and last MO to keep
     573              : !> \param warn warn when the spin windows differ; false for a second call in the same run
     574              : ! **************************************************************************************************
     575          172 :    SUBROUTINE bse_window_from_dft(eigenval_scf, n_mo, homo, cutoff_occ, cutoff_empty, window, warn)
     576              : 
     577              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: eigenval_scf
     578              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: n_mo, homo
     579              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_occ, cutoff_empty
     580              :       TYPE(mo_window_type), INTENT(OUT)                  :: window
     581              :       LOGICAL, INTENT(IN)                                :: warn
     582              : 
     583              :       INTEGER                                            :: ispin, nspins
     584              :       LOGICAL                                            :: spins_differ
     585              :       TYPE(mo_window_type), ALLOCATABLE, DIMENSION(:)    :: spin_windows
     586              : 
     587          172 :       nspins = SIZE(homo)
     588          728 :       ALLOCATE (spin_windows(nspins))
     589          384 :       DO ispin = 1, nspins
     590              :          CALL determine_mo_window(eigenval_scf(:, ispin), n_mo(ispin), homo(ispin), &
     591          384 :                                   cutoff_occ, cutoff_empty, spin_windows(ispin))
     592              :       END DO
     593          172 :       IF (nspins > 1) THEN
     594           40 :          CALL combine_mo_windows(spin_windows, window, spins_differ)
     595           40 :          IF (spins_differ .AND. warn) THEN
     596            4 :             CPWARN("BSE: spin-resolved active MO cutoff candidates differ; using combined window.")
     597              :          END IF
     598              :       ELSE
     599          132 :          window = spin_windows(1)
     600              :       END IF
     601          172 :       DEALLOCATE (spin_windows)
     602              : 
     603          172 :    END SUBROUTINE bse_window_from_dft
     604              : 
     605              : ! **************************************************************************************************
     606              : !> \brief The active MO window of bse_window_from_dft, from the DFT eigenvalues of the MO sets
     607              : !> \param mos MO sets per spin
     608              : !> \param cutoff_occ ENERGY_CUTOFF_OCC in Hartree, zero or negative for no cutoff
     609              : !> \param cutoff_empty ENERGY_CUTOFF_EMPTY in Hartree, zero or negative for no cutoff
     610              : !> \param window first and last MO to keep, one absolute range for every spin
     611              : !> \param warn warn when the spin windows differ
     612              : ! **************************************************************************************************
     613          106 :    SUBROUTINE bse_window_from_mos(mos, cutoff_occ, cutoff_empty, window, warn)
     614              : 
     615              :       TYPE(mo_set_type), DIMENSION(:), INTENT(IN)        :: mos
     616              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_occ, cutoff_empty
     617              :       TYPE(mo_window_type), INTENT(OUT)                  :: window
     618              :       LOGICAL, INTENT(IN)                                :: warn
     619              : 
     620              :       INTEGER                                            :: ispin, nspins
     621              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: homo, n_mo
     622          106 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: eigenval_dft
     623          106 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: eigenvalues
     624              : 
     625          106 :       nspins = SIZE(mos)
     626          424 :       ALLOCATE (homo(nspins), n_mo(nspins))
     627          236 :       DO ispin = 1, nspins
     628          130 :          CALL get_mo_set(mos(ispin), nmo=n_mo(ispin), homo=homo(ispin), eigenvalues=eigenvalues)
     629          448 :          IF (ispin == 1) ALLOCATE (eigenval_dft(n_mo(1), nspins))
     630         4326 :          eigenval_dft(:, ispin) = eigenvalues(1:n_mo(ispin))
     631              :       END DO
     632          106 :       CALL bse_window_from_dft(eigenval_dft, n_mo, homo, cutoff_occ, cutoff_empty, window, warn)
     633          106 :       DEALLOCATE (homo, n_mo, eigenval_dft)
     634              : 
     635          212 :    END SUBROUTINE bse_window_from_mos
     636              : 
     637              : ! **************************************************************************************************
     638              : !> \brief Determines indices within the given energy cutoffs and truncates Eigenvalues and matrices
     639              : !> \param fm_S_ia_full ...
     640              : !> \param fm_S_ij_full ...
     641              : !> \param fm_S_ab_full ...
     642              : !> \param Eigenval_scf ...
     643              : !> \param Eigenval ...
     644              : !> \param Eigenval_reduced_scf DFT energies of the window, allocated here
     645              : !> \param Eigenval_reduced_qp quasiparticle energies of the window, allocated here
     646              : !> \param homo ...
     647              : !> \param virtual ...
     648              : !> \param dimen_RI ...
     649              : !> \param unit_nr ...
     650              : !> \param ispin spin of the slabs, selects the GW-corrected virtual count
     651              : !> \param bse_env the BSE environment (settings; the GW-corrected level counts); receives the
     652              : !>        window's slabs fm_S_ia, fm_S_ij, fm_S_ab and its level counts homo_red, virt_red of spin ispin
     653              : !> \param window Absolute MO window to retain.
     654              : !> \param print_window Whether to print the cutoff summary.
     655              : ! **************************************************************************************************
     656          136 :    SUBROUTINE truncate_BSE_matrices(fm_S_ia_full, fm_S_ij_full, fm_S_ab_full, &
     657           68 :                                     Eigenval_scf, Eigenval, Eigenval_reduced_scf, Eigenval_reduced_qp, &
     658              :                                     homo, virtual, dimen_RI, unit_nr, &
     659              :                                     ispin, &
     660              :                                     bse_env, &
     661              :                                     window, print_window)
     662              : 
     663              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_S_ia_full, fm_S_ij_full, fm_S_ab_full
     664              :       REAL(KIND=dp), DIMENSION(:)                        :: Eigenval_scf, Eigenval
     665              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: Eigenval_reduced_scf, Eigenval_reduced_qp
     666              :       INTEGER, INTENT(IN)                                :: homo, virtual, dimen_RI, unit_nr, ispin
     667              :       TYPE(bse_env_type), INTENT(INOUT)                  :: bse_env
     668              :       TYPE(mo_window_type), INTENT(IN)                   :: window
     669              :       LOGICAL, INTENT(IN)                                :: print_window
     670              : 
     671              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'truncate_BSE_matrices'
     672              : 
     673              :       INTEGER                                            :: handle, homo_incl, homo_red, virt_incl, &
     674              :                                                             virt_red
     675              :       TYPE(cp_blacs_env_type), POINTER                   :: context
     676              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_ab, fm_struct_ia, fm_struct_ij
     677              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     678              : 
     679           68 :       CALL timeset(routineN, handle)
     680              : 
     681           68 :       homo_incl = window%first_mo
     682           68 :       virt_incl = window%last_mo - homo
     683           68 :       homo_red = homo - homo_incl + 1
     684           68 :       virt_red = virt_incl
     685              : 
     686           68 :       IF (print_window .AND. unit_nr > 0) THEN
     687           22 :          IF (bse_env%bse_cutoff_occ > 0) THEN
     688           17 :             WRITE (unit_nr, '(T2,A4,T7,A29,T71,F10.3)') 'BSE|', 'Cutoff occupied orbitals [eV]', &
     689           34 :                bse_env%bse_cutoff_occ*evolt
     690              :          ELSE
     691            5 :             WRITE (unit_nr, '(T2,A4,T7,A37)') 'BSE|', 'No cutoff given for occupied orbitals'
     692              :          END IF
     693           22 :          IF (bse_env%bse_cutoff_empty > 0) THEN
     694           18 :             WRITE (unit_nr, '(T2,A4,T7,A26,T71,F10.3)') 'BSE|', 'Cutoff empty orbitals [eV]', &
     695           36 :                bse_env%bse_cutoff_empty*evolt
     696              :          ELSE
     697            4 :             WRITE (unit_nr, '(T2,A4,T7,A34)') 'BSE|', 'No cutoff given for empty orbitals'
     698              :          END IF
     699           22 :          WRITE (unit_nr, '(T2,A4,T7,A20,T71,I10)') 'BSE|', 'First occupied index', homo_incl
     700           22 :          WRITE (unit_nr, '(T2,A4,T7,A32,T71,I10)') 'BSE|', 'Last empty index (not MO index!)', virt_incl
     701           22 :          WRITE (unit_nr, '(T2,A4,T7,A35,T71,F10.3)') 'BSE|', 'Energy of first occupied index [eV]', &
     702           44 :             Eigenval(homo_incl)*evolt
     703           22 :          WRITE (unit_nr, '(T2,A4,T7,A31,T71,F10.3)') 'BSE|', 'Energy of last empty index [eV]', &
     704           44 :             Eigenval(homo + virt_incl)*evolt
     705           22 :          WRITE (unit_nr, '(T2,A4,T7,A54,T71,F10.3)') 'BSE|', &
     706           22 :             'Energy difference of first occupied index to HOMO [eV]', &
     707           44 :             -(Eigenval(homo_incl) - Eigenval(homo))*evolt
     708           22 :          WRITE (unit_nr, '(T2,A4,T7,A50,T71,F10.3)') 'BSE|', &
     709           22 :             'Energy difference of last empty index to LUMO [eV]', &
     710           44 :             (Eigenval(homo + virt_incl) - Eigenval(homo + 1))*evolt
     711           22 :          WRITE (unit_nr, '(T2,A4,T7,A35,T71,I10)') 'BSE|', 'Number of GW-corrected occupied MOs', &
     712           44 :             bse_env%gw_corr_lev_occ
     713           22 :          WRITE (unit_nr, '(T2,A4,T7,A32,T71,I10)') 'BSE|', 'Number of GW-corrected empty MOs', &
     714           44 :             bse_env%gw_corr_lev_virt(ispin)
     715           22 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     716              :       END IF
     717           68 :       IF (unit_nr > 0) THEN
     718           34 :          IF (homo - homo_incl + 1 > bse_env%gw_corr_lev_occ) THEN
     719            0 :             CPABORT("Number of GW-corrected occupied MOs too small for chosen BSE cutoff")
     720              :          END IF
     721           34 :          IF (virt_incl > bse_env%gw_corr_lev_virt(ispin)) THEN
     722            0 :             CPABORT("Number of GW-corrected virtual MOs too small for chosen BSE cutoff")
     723              :          END IF
     724              :       END IF
     725              :       !Truncate full fm_S matrices
     726              :       !Allocate new truncated matrices of proper size
     727           68 :       para_env => fm_S_ia_full%matrix_struct%para_env
     728           68 :       context => fm_S_ia_full%matrix_struct%context
     729              : 
     730           68 :       CALL cp_fm_struct_create(fm_struct_ia, para_env, context, dimen_RI, homo_red*virt_red)
     731           68 :       CALL cp_fm_struct_create(fm_struct_ij, para_env, context, dimen_RI, homo_red*homo_red)
     732           68 :       CALL cp_fm_struct_create(fm_struct_ab, para_env, context, dimen_RI, virt_red*virt_red)
     733              : 
     734           68 :       CALL cp_fm_create(bse_env%fm_S_ia(ispin), fm_struct_ia, name="fm_S_trunc", set_zero=.TRUE.)
     735           68 :       CALL cp_fm_create(bse_env%fm_S_ij(ispin), fm_struct_ij, name="fm_S_ij_trunc", set_zero=.TRUE.)
     736           68 :       CALL cp_fm_create(bse_env%fm_S_ab(ispin), fm_struct_ab, name="fm_S_ab_trunc", set_zero=.TRUE.)
     737              : 
     738              :       !Copy parts of original matrices to truncated ones
     739           68 :       IF (bse_env%bse_cutoff_occ > 0 .OR. bse_env%bse_cutoff_empty > 0) THEN
     740              :          !Truncate eigenvals
     741          208 :          ALLOCATE (Eigenval_reduced_scf(homo_red + virt_red), Eigenval_reduced_qp(homo_red + virt_red))
     742         1174 :          Eigenval_reduced_scf(:) = Eigenval_scf(homo_incl:homo + virt_incl)
     743         1174 :          Eigenval_reduced_qp(:) = Eigenval(homo_incl:homo + virt_incl)
     744              : 
     745              :          CALL cp_fm_permute(bse_env%fm_S_ia(ispin), fm_S_ia_full, 1.0_dp, 1, virtual, 1, virt_red, [1, 2, 3, 4], &
     746              :                             first_index=[1, 1, homo_incl, 1], &
     747          468 :                             last_index=[dimen_RI, 1, (homo_incl) + homo_red - 1, virt_red])
     748              :          CALL cp_fm_permute(bse_env%fm_S_ij(ispin), fm_S_ij_full, 1.0_dp, 1, homo, 1, homo_red, [1, 2, 3, 4], &
     749              :                             first_index=[1, 1, homo_incl, homo_incl], &
     750          468 :                             last_index=[dimen_RI, 1, (homo_incl) + homo_red - 1, (homo_incl) + homo_red - 1])
     751              :          CALL cp_fm_permute(bse_env%fm_S_ab(ispin), fm_S_ab_full, 1.0_dp, 1, bse_env%gw_corr_lev_virt(ispin), 1, &
     752              :                             virt_red, [1, 2, 3, 4], first_index=[1, 1, 1, 1], &
     753          260 :                             last_index=[dimen_RI, 1, virt_red, virt_red])
     754              : 
     755              :       ELSE
     756           16 :          IF (unit_nr > 0) THEN
     757            8 :             WRITE (unit_nr, '(T2,A4,T7,A37)') 'BSE|', 'No truncation of BSE matrices applied'
     758            8 :             WRITE (unit_nr, '(T2,A4)') 'BSE|'
     759              :          END IF
     760           64 :          ALLOCATE (Eigenval_reduced_scf(homo_red + virt_red), Eigenval_reduced_qp(homo_red + virt_red))
     761          696 :          Eigenval_reduced_scf(:) = Eigenval_scf(:)
     762          696 :          Eigenval_reduced_qp(:) = Eigenval(:)
     763              :          CALL cp_fm_to_fm_submat_general(fm_S_ia_full, bse_env%fm_S_ia(ispin), dimen_RI, homo_red*virt_red, &
     764           16 :                                          1, 1, 1, 1, context)
     765              :          CALL cp_fm_to_fm_submat_general(fm_S_ij_full, bse_env%fm_S_ij(ispin), dimen_RI, homo_red*homo_red, &
     766           16 :                                          1, 1, 1, 1, context)
     767              :          CALL cp_fm_to_fm_submat_general(fm_S_ab_full, bse_env%fm_S_ab(ispin), dimen_RI, virt_red*virt_red, &
     768           16 :                                          1, 1, 1, 1, context)
     769              :       END IF
     770              : 
     771           68 :       bse_env%homo_red(ispin) = homo_red
     772           68 :       bse_env%virt_red(ispin) = virt_red
     773              : 
     774           68 :       CALL cp_fm_struct_release(fm_struct_ia)
     775           68 :       CALL cp_fm_struct_release(fm_struct_ij)
     776           68 :       CALL cp_fm_struct_release(fm_struct_ab)
     777              : 
     778           68 :       NULLIFY (para_env)
     779           68 :       NULLIFY (context)
     780              : 
     781           68 :       CALL timestop(handle)
     782              : 
     783           68 :    END SUBROUTINE truncate_BSE_matrices
     784              : 
     785              : ! **************************************************************************************************
     786              : !> \brief ...
     787              : !> \param fm_eigvec ...
     788              : !> \param fm_eigvec_reshuffled ...
     789              : !> \param homo ...
     790              : !> \param virtual ...
     791              : !> \param n_exc ...
     792              : !> \param do_transpose ...
     793              : ! **************************************************************************************************
     794          900 :    SUBROUTINE reshuffle_eigvec(fm_eigvec, fm_eigvec_reshuffled, homo, virtual, n_exc, do_transpose)
     795              : 
     796              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_eigvec
     797              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_eigvec_reshuffled
     798              :       INTEGER, INTENT(IN)                                :: homo, virtual, n_exc
     799              :       LOGICAL, INTENT(IN)                                :: do_transpose
     800              : 
     801              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'reshuffle_eigvec'
     802              : 
     803              :       INTEGER                                            :: handle, my_m_col, my_n_row
     804              :       INTEGER, DIMENSION(4)                              :: reordering
     805              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_eigvec_col, &
     806              :                                                             fm_struct_eigvec_reshuffled
     807              :       TYPE(cp_fm_type)                                   :: fm_eigvec_col
     808              : 
     809          300 :       CALL timeset(routineN, handle)
     810              : 
     811              :       ! Define reordering:
     812              :       ! (ia,11) to (a1,i1) for transposition
     813              :       ! (ia,11) to (i1,a1) for default
     814          300 :       IF (do_transpose) THEN
     815           50 :          reordering = [2, 3, 1, 4]
     816           50 :          my_n_row = virtual
     817           50 :          my_m_col = homo
     818              :       ELSE
     819          250 :          reordering = [1, 3, 2, 4]
     820          250 :          my_n_row = homo
     821          250 :          my_m_col = virtual
     822              :       END IF
     823              : 
     824              :       CALL cp_fm_struct_create(fm_struct_eigvec_col, &
     825              :                                fm_eigvec%matrix_struct%para_env, fm_eigvec%matrix_struct%context, &
     826          300 :                                homo*virtual, 1)
     827              :       CALL cp_fm_struct_create(fm_struct_eigvec_reshuffled, &
     828              :                                fm_eigvec%matrix_struct%para_env, fm_eigvec%matrix_struct%context, &
     829          300 :                                my_n_row, my_m_col)
     830              : 
     831              :       ! Resort indices
     832          300 :       CALL cp_fm_create(fm_eigvec_col, fm_struct_eigvec_col, name="BSE_column_vector")
     833          300 :       CALL cp_fm_set_all(fm_eigvec_col, 0.0_dp)
     834          300 :       CALL cp_fm_create(fm_eigvec_reshuffled, fm_struct_eigvec_reshuffled, name="BSE_reshuffled_eigenvector")
     835          300 :       CALL cp_fm_set_all(fm_eigvec_reshuffled, 0.0_dp)
     836              :       ! Fill matrix
     837              :       CALL cp_fm_to_fm_submat(fm_eigvec, fm_eigvec_col, &
     838              :                               homo*virtual, 1, &
     839              :                               1, n_exc, &
     840          300 :                               1, 1)
     841              :       ! Reshuffle
     842          300 :       CALL cp_fm_permute(fm_eigvec_reshuffled, fm_eigvec_col, 1.0_dp, virtual, 1, 1, 1, reordering)
     843              : 
     844          300 :       CALL cp_fm_release(fm_eigvec_col)
     845          300 :       CALL cp_fm_struct_release(fm_struct_eigvec_col)
     846          300 :       CALL cp_fm_struct_release(fm_struct_eigvec_reshuffled)
     847              : 
     848          300 :       CALL timestop(handle)
     849              : 
     850          300 :    END SUBROUTINE reshuffle_eigvec
     851              : 
     852              : ! **************************************************************************************************
     853              : !> \brief Borrowed from the tddfpt module with slight adaptions
     854              : !> \param bse_env the BSE environment (the QS objects the cube writer reads)
     855              : !> \param mos ...
     856              : !> \param istate ...
     857              : !> \param info_approximation ...
     858              : !> \param stride ...
     859              : !> \param append_cube ...
     860              : !> \param print_section ...
     861              : ! **************************************************************************************************
     862            0 :    SUBROUTINE print_bse_nto_cubes(bse_env, mos, istate, info_approximation, &
     863              :                                   stride, append_cube, print_section)
     864              : 
     865              :       TYPE(bse_env_type), INTENT(IN)                     :: bse_env
     866              :       TYPE(mo_set_type), DIMENSION(:), INTENT(IN)        :: mos
     867              :       INTEGER, INTENT(IN)                                :: istate
     868              :       CHARACTER(LEN=10)                                  :: info_approximation
     869              :       INTEGER, DIMENSION(:), POINTER                     :: stride
     870              :       LOGICAL, INTENT(IN)                                :: append_cube
     871              :       TYPE(section_vals_type), POINTER                   :: print_section
     872              : 
     873              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_bse_nto_cubes'
     874              : 
     875              :       CHARACTER(LEN=default_path_length)                 :: filename, info_approx_trunc, &
     876              :                                                             my_pos_cube, title
     877              :       INTEGER                                            :: handle, i, iset, nmo, unit_nr_cube
     878              :       LOGICAL                                            :: mpi_io
     879            0 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     880              :       TYPE(cell_type), POINTER                           :: cell
     881              :       TYPE(cp_fm_type), POINTER                          :: mo_coeff
     882              :       TYPE(cp_logger_type), POINTER                      :: logger
     883              :       TYPE(dft_control_type), POINTER                    :: dft_control
     884              :       TYPE(particle_list_type), POINTER                  :: particles
     885            0 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     886              :       TYPE(pw_c1d_gs_type)                               :: wf_g
     887              :       TYPE(pw_env_type), POINTER                         :: pw_env
     888            0 :       TYPE(pw_pool_p_type), DIMENSION(:), POINTER        :: pw_pools
     889              :       TYPE(pw_pool_type), POINTER                        :: auxbas_pw_pool
     890              :       TYPE(pw_r3d_rs_type)                               :: wf_r
     891            0 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     892              :       TYPE(qs_subsys_type), POINTER                      :: subsys
     893              : 
     894            0 :       logger => cp_get_default_logger()
     895            0 :       CALL timeset(routineN, handle)
     896              : 
     897            0 :       dft_control => bse_env%dft_control
     898              :       ! PROPERTIES%BSE runs after the GW released the grids; its requirement reader refuses cube files
     899            0 :       IF (.NOT. ASSOCIATED(bse_env%pw_env)) THEN
     900            0 :          CPABORT("NTO cube files need the SCF grids: set NTO_ANALYSIS%CUBE_FILES to F")
     901              :       END IF
     902            0 :       pw_env => bse_env%pw_env
     903            0 :       CALL pw_env_get(pw_env, auxbas_pw_pool=auxbas_pw_pool, pw_pools=pw_pools)
     904            0 :       CALL auxbas_pw_pool%create_pw(wf_r)
     905            0 :       CALL auxbas_pw_pool%create_pw(wf_g)
     906              : 
     907            0 :       subsys => bse_env%subsys
     908            0 :       CALL qs_subsys_get(subsys, particles=particles)
     909              : 
     910            0 :       my_pos_cube = "REWIND"
     911            0 :       IF (append_cube) THEN
     912            0 :          my_pos_cube = "APPEND"
     913              :       END IF
     914              : 
     915              :       CALL qs_subsys_get(subsys, atomic_kind_set=atomic_kind_set, qs_kind_set=qs_kind_set, &
     916            0 :                          cell=cell, particle_set=particle_set)
     917              : 
     918            0 :       DO iset = 1, 2
     919            0 :          CALL get_mo_set(mo_set=mos(iset), mo_coeff=mo_coeff, nmo=nmo)
     920            0 :          DO i = 1, nmo
     921              :             CALL calculate_wavefunction(mo_coeff, i, wf_r, wf_g, atomic_kind_set, qs_kind_set, &
     922            0 :                                         cell, dft_control, particle_set, pw_env)
     923            0 :             IF (iset == 1) THEN
     924            0 :                WRITE (filename, '(A6,I3.3,A5,I2.2,a11)') "_NEXC_", istate, "_NTO_", i, "_Hole_State"
     925            0 :             ELSE IF (iset == 2) THEN
     926            0 :                WRITE (filename, '(A6,I3.3,A5,I2.2,a15)') "_NEXC_", istate, "_NTO_", i, "_Particle_State"
     927              :             END IF
     928            0 :             info_approx_trunc = TRIM(ADJUSTL(info_approximation))
     929            0 :             info_approx_trunc = info_approx_trunc(2:LEN_TRIM(info_approx_trunc) - 1)
     930            0 :             filename = TRIM(info_approx_trunc)//TRIM(filename)
     931            0 :             mpi_io = .TRUE.
     932              :             unit_nr_cube = cp_print_key_unit_nr(logger, print_section, '', extension=".cube", &
     933              :                                                 middle_name=TRIM(filename), file_position=my_pos_cube, &
     934            0 :                                                 log_filename=.FALSE., ignore_should_output=.TRUE., mpi_io=mpi_io)
     935            0 :             IF (iset == 1) THEN
     936            0 :                WRITE (title, *) "Natural Transition Orbital Hole State", i
     937            0 :             ELSE IF (iset == 2) THEN
     938            0 :                WRITE (title, *) "Natural Transition Orbital Particle State", i
     939              :             END IF
     940            0 :             CALL cp_pw_to_cube(wf_r, unit_nr_cube, title, particles=particles, stride=stride, mpi_io=mpi_io)
     941              :             CALL cp_print_key_finished_output(unit_nr_cube, logger, print_section, '', &
     942            0 :                                               ignore_should_output=.TRUE., mpi_io=mpi_io)
     943              :          END DO
     944              :       END DO
     945              : 
     946            0 :       CALL auxbas_pw_pool%give_back_pw(wf_g)
     947            0 :       CALL auxbas_pw_pool%give_back_pw(wf_r)
     948              : 
     949            0 :       CALL timestop(handle)
     950            0 :    END SUBROUTINE print_bse_nto_cubes
     951              : 
     952              : ! **************************************************************************************************
     953              : !> \brief Checks BSE input section and adapts them if necessary
     954              : !> \param homo ...
     955              : !> \param virtual ...
     956              : !> \param unit_nr ...
     957              : !> \param bse_env the BSE environment; the settings are normalised in place
     958              : ! **************************************************************************************************
     959          124 :    SUBROUTINE adapt_BSE_input_params(homo, virtual, unit_nr, bse_env)
     960              : 
     961              :       INTEGER, INTENT(IN)                                :: homo, virtual, unit_nr
     962              :       TYPE(bse_env_type), INTENT(INOUT)                  :: bse_env
     963              : 
     964              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'adapt_BSE_input_params'
     965              : 
     966              :       INTEGER                                            :: handle, i, j, n, n_exc_max, &
     967              :                                                             ndim_periodic_cell, &
     968              :                                                             ndim_periodic_poisson, &
     969              :                                                             num_state_list_exceptions
     970              :       LOGICAL                                            :: explicit
     971              :       TYPE(cell_type), POINTER                           :: cell_ref
     972              : 
     973           62 :       CALL timeset(routineN, handle)
     974              :       ! the Poisson periodicity was read from the input (finish_bse_env): the grids may be released
     975           62 :       NULLIFY (cell_ref)
     976           62 :       CALL qs_subsys_get(bse_env%subsys, cell_ref=cell_ref)
     977           62 :       ndim_periodic_poisson = bse_env%n_periodic_poisson
     978          248 :       ndim_periodic_cell = SUM(cell_ref%perd(1:3)) ! Borrowed from cell_methods.F/write_cell_low
     979              : 
     980              :       ! The iterative solver computes the NUM_EXC_EN lowest excitations only
     981           62 :       n_exc_max = homo*virtual
     982           62 :       IF (bse_env%bse_diag_method == bse_iterdiag) THEN
     983            8 :          n_exc_max = MIN(n_exc_max, bse_env%num_exc_en)
     984              :       END IF
     985              : 
     986              :       ! Handle negative NUM_PRINT_EXC
     987           62 :       IF (bse_env%num_print_exc < 0 .OR. &
     988              :           bse_env%num_print_exc > n_exc_max) THEN
     989           14 :          bse_env%num_print_exc = n_exc_max
     990           14 :          IF (unit_nr > 0) THEN
     991              :             CALL cp_hint(__LOCATION__, &
     992              :                          "Keyword NUM_PRINT_EXC is either negative or too large. "// &
     993            7 :                          "Printing all computed excitations.")
     994              :          END IF
     995              :       END IF
     996              : 
     997              :       ! Default to NUM_PRINT_EXC if too large or negative,
     998              :       ! but only if NTOs are called - would be confusing for the user otherwise
     999              :       ! Prepare and adapt user inputs for NTO analysis
    1000              :       ! Logic: Explicit state list overrides NUM_PRINT_EXC_NTOS
    1001              :       !        If only NUM_PRINT_EXC_NTOS is given, we write the array 1,...,NUM_PRINT_EXC_NTOS to
    1002              :       !        bse_nto_state_list
    1003           62 :       IF (bse_env%do_nto_analysis) THEN
    1004            4 :          IF (bse_env%explicit_nto_list) THEN
    1005            0 :             IF (bse_env%num_print_exc_ntos > 0) THEN
    1006            0 :                IF (unit_nr > 0) THEN
    1007              :                   CALL cp_hint(__LOCATION__, &
    1008              :                                "Keywords NUM_PRINT_EXC_NTOS and STATE_LIST are both given in input. "// &
    1009            0 :                                "Overriding NUM_PRINT_EXC_NTOS.")
    1010              :                END IF
    1011              :             END IF
    1012              :             ! Check if all states are within the range
    1013              :             ! Count them and initialize new array afterwards
    1014            0 :             num_state_list_exceptions = 0
    1015            0 :             DO i = 1, SIZE(bse_env%bse_nto_state_list)
    1016            0 :                IF (bse_env%bse_nto_state_list(i) < 1 .OR. &
    1017            0 :                    bse_env%bse_nto_state_list(i) > bse_env%num_print_exc) THEN
    1018            0 :                   num_state_list_exceptions = num_state_list_exceptions + 1
    1019              :                END IF
    1020              :             END DO
    1021            0 :             IF (num_state_list_exceptions > 0) THEN
    1022            0 :                IF (unit_nr > 0) THEN
    1023              :                   CALL cp_hint(__LOCATION__, &
    1024              :                                "STATE_LIST contains indices outside the range of included excitation levels. "// &
    1025            0 :                                "Ignoring these states.")
    1026              :                END IF
    1027              :             END IF
    1028            0 :             n = SIZE(bse_env%bse_nto_state_list) - num_state_list_exceptions
    1029            0 :             ALLOCATE (bse_env%bse_nto_state_list_final(n))
    1030            0 :             bse_env%bse_nto_state_list_final(:) = 0
    1031              :             i = 1
    1032            0 :             DO j = 1, SIZE(bse_env%bse_nto_state_list)
    1033            0 :                IF (bse_env%bse_nto_state_list(j) >= 1 .AND. &
    1034            0 :                    bse_env%bse_nto_state_list(j) <= bse_env%num_print_exc) THEN
    1035            0 :                   bse_env%bse_nto_state_list_final(i) = bse_env%bse_nto_state_list(j)
    1036            0 :                   i = i + 1
    1037              :                END IF
    1038              :             END DO
    1039              : 
    1040            0 :             bse_env%num_print_exc_ntos = SIZE(bse_env%bse_nto_state_list_final)
    1041              :          ELSE
    1042            4 :             IF (bse_env%num_print_exc_ntos > bse_env%num_print_exc .OR. &
    1043              :                 bse_env%num_print_exc_ntos < 0) THEN
    1044            4 :                bse_env%num_print_exc_ntos = bse_env%num_print_exc
    1045              :             END IF
    1046           12 :             ALLOCATE (bse_env%bse_nto_state_list_final(bse_env%num_print_exc_ntos))
    1047          104 :             DO i = 1, bse_env%num_print_exc_ntos
    1048          104 :                bse_env%bse_nto_state_list_final(i) = i
    1049              :             END DO
    1050              :          END IF
    1051              :       END IF
    1052              : 
    1053              :       ! Takes care of triplet states, when oscillator strengths are 0
    1054           62 :       IF (bse_env%bse_spin_config /= 0 .AND. &
    1055              :           bse_env%eps_nto_osc_str > 0) THEN
    1056            0 :          IF (unit_nr > 0) THEN
    1057              :             CALL cp_warn(__LOCATION__, &
    1058              :                          "Cannot apply EPS_OSC_STR for Triplet excitations. "// &
    1059            0 :                          "Resetting EPS_OSC_STR to default.")
    1060              :          END IF
    1061            0 :          bse_env%eps_nto_osc_str = -1.0_dp
    1062              :       END IF
    1063              : 
    1064              :       ! Take care of number for computed exciton descriptors
    1065           62 :       IF (bse_env%num_print_exc_descr < 0 .OR. &
    1066              :           bse_env%num_print_exc_descr > bse_env%num_print_exc) THEN
    1067            4 :          IF (unit_nr > 0) THEN
    1068              :             CALL cp_hint(__LOCATION__, &
    1069              :                          "Keyword NUM_PRINT_EXC_DESCR is either negative or too large. "// &
    1070            2 :                          "Printing exciton descriptors up to NUM_PRINT_EXC.")
    1071              :          END IF
    1072            4 :          bse_env%num_print_exc_descr = bse_env%num_print_exc
    1073              :       END IF
    1074              : 
    1075              :       ! the keyword defaults to true, so only an explicit request is worth a warning
    1076           62 :       CALL section_vals_val_get(bse_env%bse_section, "PRINT_DIRECTIONAL_CROSSCORRELATION", explicit=explicit)
    1077           62 :       IF (explicit .AND. bse_env%print_directional_crosscorrelation .AND. &
    1078              :           .NOT. bse_env%print_directional_exc_descr) THEN
    1079            0 :          IF (unit_nr > 0) THEN
    1080              :             CALL cp_warn(__LOCATION__, &
    1081            0 :                          "PRINT_DIRECTIONAL_CROSSCORRELATION has no effect without PRINT_DIRECTIONAL_EXC_DESCR.")
    1082              :          END IF
    1083              :       END IF
    1084              : 
    1085              :       ! Add warning for usage of KS energies
    1086           62 :       IF (bse_env%use_ks_energies) THEN
    1087            4 :          IF (unit_nr > 0) THEN
    1088              :             CALL cp_warn(__LOCATION__, &
    1089              :                          "Using KS energies for BSE calculations. Therefore, no quantities "// &
    1090            2 :                          "of the preceeding GW calculation enter the BSE.")
    1091              :          END IF
    1092              :       END IF
    1093              : 
    1094              :       ! Add warning if periodic calculation is invoked
    1095           62 :       IF (ndim_periodic_poisson /= 0) THEN
    1096            0 :          IF (unit_nr > 0) THEN
    1097              :             CALL cp_warn(__LOCATION__, &
    1098              :                          "Poisson solver should be invoked by PERIODIC NONE. "// &
    1099              :                          "The applied length gauge might give misleading results for "// &
    1100            0 :                          "oscillator strengths.")
    1101              :          END IF
    1102              :       END IF
    1103           62 :       IF (ndim_periodic_cell /= 0) THEN
    1104            0 :          IF (unit_nr > 0) THEN
    1105              :             CALL cp_warn(__LOCATION__, &
    1106              :                          "CELL in SUBSYS should be invoked with PERIODIC NONE. "// &
    1107              :                          "The applied length gauge might give misleading results for "// &
    1108            0 :                          "oscillator strengths.")
    1109              :          END IF
    1110              :       END IF
    1111              : 
    1112           62 :       CALL timestop(handle)
    1113           62 :    END SUBROUTINE adapt_BSE_input_params
    1114              : 
    1115              : ! **************************************************************************************************
    1116              : !> \brief Resolve SCREENING_IN_W and its factor before constructing the dielectric matrix or slabs.
    1117              : !> \param bse_env screening method and factor, normalised in place
    1118              : !> \param unit_nr output unit, positive on the writing rank only
    1119              : ! **************************************************************************************************
    1120           62 :    SUBROUTINE adapt_BSE_screening(bse_env, unit_nr)
    1121              :       TYPE(bse_env_type), INTENT(INOUT)                  :: bse_env
    1122              :       INTEGER, INTENT(IN)                                :: unit_nr
    1123              : 
    1124              :       ! Handle screening factor options
    1125           62 :       IF (bse_env%screening_factor > 0.0_dp) THEN
    1126            2 :          IF (bse_env%screening_method /= bse_screening_alpha) THEN
    1127            0 :             IF (unit_nr > 0) THEN
    1128              :                CALL cp_warn(__LOCATION__, &
    1129              :                             "Screening factor is only supported for &SCREENING_IN_W ALPHA. "// &
    1130            0 :                             "Resetting SCREENING_IN_W to ALPHA.")
    1131              :             END IF
    1132            0 :             bse_env%screening_method = bse_screening_alpha
    1133              :          END IF
    1134            2 :          IF (bse_env%screening_factor > 1.0_dp) THEN
    1135            0 :             IF (unit_nr > 0) THEN
    1136              :                CALL cp_warn(__LOCATION__, &
    1137            0 :                             "Screening factor is larger than 1.0. ")
    1138              :             END IF
    1139              :          END IF
    1140              :       END IF
    1141              : 
    1142           62 :       IF (bse_env%screening_factor < 0.0_dp .AND. &
    1143              :           bse_env%screening_method == bse_screening_alpha) THEN
    1144            0 :          IF (unit_nr > 0) THEN
    1145              :             CALL cp_warn(__LOCATION__, &
    1146            0 :                          "Screening factor is negative. Defaulting to 0.25")
    1147              :          END IF
    1148            0 :          bse_env%screening_factor = 0.25_dp
    1149              :       END IF
    1150              : 
    1151           62 :       IF (bse_env%screening_factor == 0.0_dp) THEN
    1152              :          ! Use RPA internally in this case
    1153            0 :          bse_env%screening_method = bse_screening_rpa
    1154              :       END IF
    1155           62 :       IF (bse_env%screening_factor == 1.0_dp) THEN
    1156              :          ! Use TDHF internally in this case
    1157            0 :          bse_env%screening_method = bse_screening_tdhf
    1158              :       END IF
    1159              : 
    1160           62 :    END SUBROUTINE adapt_BSE_screening
    1161              : 
    1162              : ! **************************************************************************************************
    1163              : 
    1164              : ! **************************************************************************************************
    1165              : !> \brief The multipoles in the MO window, D^k_pq = sum_µν C_µp M^k_µν C_νq, cut to the ai, ij and ab
    1166              : !>        blocks of the window; M^k_µν are the AO multipoles of get_multipoles_ao
    1167              : !> \param fm_multipole_ai_trunc D^k_ai, one entry per component k
    1168              : !> \param fm_multipole_ij_trunc D^k_ij
    1169              : !> \param fm_multipole_ab_trunc D^k_ab
    1170              : !> \param matrix_multipole M^k_µν, one entry per component k (x, y, z, then xx, xy, xz, yy, yz, zz)
    1171              : !> \param mos MO sets of the reference, for the AO layout and the occupied count
    1172              : !> \param mo_coeff C_µp of the spin
    1173              : !> \param homo_red occupied levels in the window
    1174              : !> \param virtual_red virtual levels in the window
    1175              : !> \param context_BSE ...
    1176              : !> \param ispin spin channel whose mo_set supplies homo/nao (default 1); open-shell beta needs 2
    1177              : ! **************************************************************************************************
    1178           80 :    SUBROUTINE get_multipoles_mo(fm_multipole_ai_trunc, fm_multipole_ij_trunc, fm_multipole_ab_trunc, &
    1179           80 :                                 matrix_multipole, mos, mo_coeff, &
    1180              :                                 homo_red, virtual_red, context_BSE, ispin)
    1181              : 
    1182              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:), &
    1183              :          INTENT(INOUT)                                   :: fm_multipole_ai_trunc, &
    1184              :                                                             fm_multipole_ij_trunc, &
    1185              :                                                             fm_multipole_ab_trunc
    1186              :       TYPE(dbcsr_p_type), DIMENSION(:), INTENT(IN)       :: matrix_multipole
    1187              :       TYPE(mo_set_type), DIMENSION(:), INTENT(IN)        :: mos
    1188              :       TYPE(cp_fm_type), DIMENSION(:), INTENT(IN)         :: mo_coeff
    1189              :       INTEGER, INTENT(IN)                                :: homo_red, virtual_red
    1190              :       TYPE(cp_blacs_env_type), POINTER                   :: context_BSE
    1191              :       INTEGER, INTENT(IN), OPTIONAL                      :: ispin
    1192              : 
    1193              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'get_multipoles_mo'
    1194              : 
    1195              :       INTEGER                                            :: handle, idir, my_ispin, n_multipole, &
    1196              :                                                             n_occ, n_virt, nao, nmo_mp2
    1197              :       TYPE(cp_fm_struct_type), POINTER :: fm_struct_mp_ab_trunc, fm_struct_mp_ai_trunc, &
    1198              :          fm_struct_mp_ij_trunc, fm_struct_multipoles_ao, fm_struct_nao_nmo, fm_struct_nmo_nmo
    1199              :       TYPE(cp_fm_type)                                   :: fm_work
    1200           80 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_multipole_per_dir
    1201              :       TYPE(mp_para_env_type), POINTER                    :: para_env_BSE
    1202              : 
    1203           80 :       CALL timeset(routineN, handle)
    1204              : 
    1205           80 :       my_ispin = 1
    1206           80 :       IF (PRESENT(ispin)) my_ispin = ispin
    1207              : 
    1208              :       ! Use the same blacs environment as for the MO coefficients to ensure correct multiplication dbcsr x fm later on
    1209           80 :       fm_struct_multipoles_ao => mos(my_ispin)%mo_coeff%matrix_struct
    1210              :       ! BSE has different contexts and blacsenvs
    1211           80 :       para_env_BSE => context_BSE%para_env
    1212           80 :       n_multipole = SIZE(matrix_multipole)
    1213          516 :       ALLOCATE (fm_multipole_per_dir(n_multipole))
    1214              : 
    1215              :       ! Now we transform them to MO
    1216              :       ! n_occ is the number of occupied MOs, nao the number of all AOs
    1217              :       ! Writing homo to n_occ instead if nmo,
    1218              :       ! takes care of ADDED_MOS, which would overwrite nmo of qs_env-mos, if invoked
    1219           80 :       CALL get_mo_set(mo_set=mos(my_ispin), homo=n_occ, nao=nao)
    1220              :       ! Takes into account removed nullspace values from SVD
    1221           80 :       nmo_mp2 = mo_coeff(1)%matrix_struct%ncol_global
    1222           80 :       n_virt = nmo_mp2 - n_occ
    1223              : 
    1224              :       ! At the end, we need four different layouts of matrices in this multiplication, e.g. for a dipole:
    1225              :       ! D_pq = full multipole matrix for occupied and unoccupied
    1226              :       ! Final result:D_pq= C_{mu p}        <\mu|\vec{r}|\nu>        C_{\nu q}              EQ.I
    1227              :       !                   \_______/         \___________/          \______/
    1228              :       !                    fm_coeff            matrix_multipole              fm_coeff
    1229              :       !                    (EQ.Ia)             (EQ.Ib)              (EQ.Ia)
    1230              :       ! Intermediate work matrices:
    1231              :       ! fm_work =                 <\mu|\vec{r}|\nu>        C_{\nu q}              EQ.II
    1232              : 
    1233              :       ! Struct for the full multipole matrix
    1234              :       CALL cp_fm_struct_create(fm_struct_nao_nmo, &
    1235              :                                fm_struct_multipoles_ao%para_env, fm_struct_multipoles_ao%context, &
    1236           80 :                                nao, nmo_mp2)
    1237              :       CALL cp_fm_struct_create(fm_struct_nmo_nmo, &
    1238              :                                fm_struct_multipoles_ao%para_env, fm_struct_multipoles_ao%context, &
    1239           80 :                                nmo_mp2, nmo_mp2)
    1240              : 
    1241              :       ! At the very end, we copy the multipoles corresponding to truncated BSE indices in i and a
    1242              :       CALL cp_fm_struct_create(fm_struct_mp_ai_trunc, para_env_BSE, &
    1243           80 :                                context_BSE, virtual_red, homo_red)
    1244              :       CALL cp_fm_struct_create(fm_struct_mp_ij_trunc, para_env_BSE, &
    1245           80 :                                context_BSE, homo_red, homo_red)
    1246              :       CALL cp_fm_struct_create(fm_struct_mp_ab_trunc, para_env_BSE, &
    1247           80 :                                context_BSE, virtual_red, virtual_red)
    1248          356 :       DO idir = 1, n_multipole
    1249              :          CALL cp_fm_create(fm_multipole_ai_trunc(idir), matrix_struct=fm_struct_mp_ai_trunc, &
    1250          276 :                            name="dipoles_mo_ai_trunc")
    1251          276 :          CALL cp_fm_set_all(fm_multipole_ai_trunc(idir), 0.0_dp)
    1252              :          CALL cp_fm_create(fm_multipole_ij_trunc(idir), matrix_struct=fm_struct_mp_ij_trunc, &
    1253          276 :                            name="dipoles_mo_ij_trunc")
    1254          276 :          CALL cp_fm_set_all(fm_multipole_ij_trunc(idir), 0.0_dp)
    1255              :          CALL cp_fm_create(fm_multipole_ab_trunc(idir), matrix_struct=fm_struct_mp_ab_trunc, &
    1256          276 :                            name="dipoles_mo_ab_trunc")
    1257          356 :          CALL cp_fm_set_all(fm_multipole_ab_trunc(idir), 0.0_dp)
    1258              :       END DO
    1259              : 
    1260              :       ! Need another temporary matrix to store intermediate result from right multiplication
    1261              :       ! D = C_{mu a}        <\mu|\vec{r}|\nu>        C_{\nu i}
    1262           80 :       CALL cp_fm_create(fm_work, matrix_struct=fm_struct_nao_nmo, name="multipole_work")
    1263           80 :       CALL cp_fm_set_all(fm_work, 0.0_dp)
    1264              : 
    1265          356 :       DO idir = 1, n_multipole
    1266              :          ! Create the full multipole matrix per direction
    1267          276 :          CALL cp_fm_create(fm_multipole_per_dir(idir), matrix_struct=fm_struct_nmo_nmo, name="multipoles_mo")
    1268          276 :          CALL cp_fm_set_all(fm_multipole_per_dir(idir), 0.0_dp)
    1269              :          ! Fill final (MO) multipole matrix
    1270              :          CALL cp_dbcsr_sm_fm_multiply(matrix_multipole(idir)%matrix, mo_coeff(1), &
    1271          276 :                                       fm_work, ncol=nmo_mp2)
    1272              :          ! Now obtain the multipoles by the final multiplication;
    1273              :          ! We do that inside the loop to obtain multipoles per axis for print
    1274          276 :          CALL parallel_gemm('T', 'N', nmo_mp2, nmo_mp2, nao, 1.0_dp, mo_coeff(1), fm_work, 0.0_dp, fm_multipole_per_dir(idir))
    1275              : 
    1276              :          ! Truncate full matrix to the BSE indices
    1277              :          ! D_ai
    1278              :          CALL cp_fm_to_fm_submat_general(fm_multipole_per_dir(idir), &
    1279              :                                          fm_multipole_ai_trunc(idir), &
    1280              :                                          virtual_red, &
    1281              :                                          homo_red, &
    1282              :                                          n_occ + 1, &
    1283              :                                          n_occ - homo_red + 1, &
    1284              :                                          1, &
    1285              :                                          1, &
    1286          276 :                                          fm_multipole_per_dir(idir)%matrix_struct%context)
    1287              :          ! D_ij
    1288              :          CALL cp_fm_to_fm_submat_general(fm_multipole_per_dir(idir), &
    1289              :                                          fm_multipole_ij_trunc(idir), &
    1290              :                                          homo_red, &
    1291              :                                          homo_red, &
    1292              :                                          n_occ - homo_red + 1, &
    1293              :                                          n_occ - homo_red + 1, &
    1294              :                                          1, &
    1295              :                                          1, &
    1296          276 :                                          fm_multipole_per_dir(idir)%matrix_struct%context)
    1297              :          ! D_ab
    1298              :          CALL cp_fm_to_fm_submat_general(fm_multipole_per_dir(idir), &
    1299              :                                          fm_multipole_ab_trunc(idir), &
    1300              :                                          virtual_red, &
    1301              :                                          virtual_red, &
    1302              :                                          n_occ + 1, &
    1303              :                                          n_occ + 1, &
    1304              :                                          1, &
    1305              :                                          1, &
    1306          356 :                                          fm_multipole_per_dir(idir)%matrix_struct%context)
    1307              :       END DO
    1308              : 
    1309              :       !Release matrices and structs
    1310           80 :       NULLIFY (fm_struct_multipoles_ao)
    1311           80 :       CALL cp_fm_struct_release(fm_struct_mp_ai_trunc)
    1312           80 :       CALL cp_fm_struct_release(fm_struct_mp_ij_trunc)
    1313           80 :       CALL cp_fm_struct_release(fm_struct_mp_ab_trunc)
    1314           80 :       CALL cp_fm_struct_release(fm_struct_nao_nmo)
    1315           80 :       CALL cp_fm_struct_release(fm_struct_nmo_nmo)
    1316          356 :       DO idir = 1, n_multipole
    1317          356 :          CALL cp_fm_release(fm_multipole_per_dir(idir))
    1318              :       END DO
    1319           80 :       DEALLOCATE (fm_multipole_per_dir)
    1320           80 :       CALL cp_fm_release(fm_work)
    1321              : 
    1322           80 :       CALL timestop(handle)
    1323              : 
    1324          240 :    END SUBROUTINE get_multipoles_mo
    1325              : 
    1326              : ! **************************************************************************************************
    1327              : !> \brief Computes trace of form Tr{A^T B C} for exciton descriptors
    1328              : !> \param fm_A Full Matrix, typically X or Y, in format homo x virtual
    1329              : !> \param fm_B ...
    1330              : !> \param fm_C ...
    1331              : !> \param alpha ...
    1332              : ! **************************************************************************************************
    1333        11520 :    SUBROUTINE trace_exciton_descr(fm_A, fm_B, fm_C, alpha)
    1334              : 
    1335              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_A, fm_B, fm_C
    1336              :       REAL(KIND=dp), INTENT(OUT)                         :: alpha
    1337              : 
    1338              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'trace_exciton_descr'
    1339              : 
    1340              :       INTEGER                                            :: handle, ncol_A, ncol_B, ncol_C, nrow_A, &
    1341              :                                                             nrow_B, nrow_C
    1342              :       TYPE(cp_fm_type)                                   :: fm_work_ia
    1343              : 
    1344         1920 :       CALL timeset(routineN, handle)
    1345              : 
    1346         1920 :       CALL cp_fm_create(fm_work_ia, fm_A%matrix_struct)
    1347         1920 :       CALL cp_fm_get_info(fm_A, nrow_global=nrow_A, ncol_global=ncol_A)
    1348         1920 :       CALL cp_fm_get_info(fm_B, nrow_global=nrow_B, ncol_global=ncol_B)
    1349         1920 :       CALL cp_fm_get_info(fm_C, nrow_global=nrow_C, ncol_global=ncol_C)
    1350              : 
    1351              :       ! Check matrix sizes
    1352         1920 :       CPASSERT(nrow_A == nrow_B .AND. ncol_A == ncol_C .AND. ncol_B == nrow_C)
    1353              : 
    1354         1920 :       CALL cp_fm_set_all(fm_work_ia, 0.0_dp)
    1355              : 
    1356              :       CALL parallel_gemm("N", "N", nrow_A, ncol_A, nrow_C, 1.0_dp, &
    1357         1920 :                          fm_B, fm_C, 0.0_dp, fm_work_ia)
    1358              : 
    1359         1920 :       CALL cp_fm_trace(fm_A, fm_work_ia, alpha)
    1360              : 
    1361         1920 :       CALL cp_fm_release(fm_work_ia)
    1362              : 
    1363         1920 :       CALL timestop(handle)
    1364              : 
    1365         1920 :    END SUBROUTINE trace_exciton_descr
    1366              : 
    1367              : ! **************************************************************************************************
    1368              : !> \brief Column-concatenate per-spin ia-slabs into the joint dimen_RI x n_ov_joint slab.
    1369              : !>   Sigma-block of spin isp occupies columns offsets(isp)+1 .. offsets(isp)+n_ov(isp).
    1370              : !>   fm_S_joint must be pre-created and zeroed by the caller.
    1371              : !> \param fm_S_ia per-spin ia-slabs, shape (dimen_RI, n_ov(isp)) per spin
    1372              : !> \param offsets per-spin column offsets into fm_S_joint (0-based)
    1373              : !> \param n_ov per-spin OV-pair counts
    1374              : !> \param dimen_RI RI auxiliary basis dimension (row count)
    1375              : !> \param fm_S_joint pre-created output slab (dimen_RI x n_ov_joint)
    1376              : ! **************************************************************************************************
    1377           18 :    SUBROUTINE assemble_joint_ov_slab(fm_S_ia, offsets, n_ov, dimen_RI, fm_S_joint)
    1378              :       TYPE(cp_fm_type), DIMENSION(:), INTENT(IN)         :: fm_S_ia
    1379              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: offsets, n_ov
    1380              :       INTEGER, INTENT(IN)                                :: dimen_RI
    1381              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_S_joint
    1382              : 
    1383              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'assemble_joint_ov_slab'
    1384              : 
    1385              :       INTEGER                                            :: handle, isp
    1386              : 
    1387           18 :       CALL timeset(routineN, handle)
    1388           54 :       DO isp = 1, SIZE(fm_S_ia)
    1389           54 :          CALL cp_fm_to_fm_submat(fm_S_ia(isp), fm_S_joint, dimen_RI, n_ov(isp), 1, 1, 1, offsets(isp) + 1)
    1390              :       END DO
    1391           18 :       CALL timestop(handle)
    1392              : 
    1393           18 :    END SUBROUTINE assemble_joint_ov_slab
    1394              : 
    1395              : END MODULE bse_util
        

Generated by: LCOV version 2.0-1