LCOV - code coverage report
Current view: top level - src - gw_auto_ri_generate_RI_basis.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:92574dc) Lines: 95.9 % 1110 1064
Test Date: 2026-09-24 01:27:39 Functions: 97.8 % 45 44

            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 Automatic RI basis set optimization for molecular GW.
      10              : !> \par History
      11              : !>      09.2026 created [Jan Wilhelm]
      12              : ! **************************************************************************************************
      13              : MODULE gw_auto_ri_generate_RI_basis
      14              :    USE ai_contraction_sphi,             ONLY: ab_contract,&
      15              :                                               abc_contract
      16              :    USE atomic_kind_types,               ONLY: get_atomic_kind_set
      17              :    USE basis_set_types,                 ONLY: get_gto_basis_set,&
      18              :                                               gto_basis_set_type
      19              :    USE constants_operator,              ONLY: operator_coulomb,&
      20              :                                               operator_truncated
      21              :    USE coulomb_integral_interface,      ONLY: compute_coulomb_2c,&
      22              :                                               compute_coulomb_3c,&
      23              :                                               coulomb_integral_cleanup,&
      24              :                                               coulomb_integral_context_type,&
      25              :                                               coulomb_integral_init,&
      26              :                                               coulomb_operator_type
      27              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_type
      28              :    USE cp_dbcsr_api,                    ONLY: &
      29              :         dbcsr_create, dbcsr_distribution_get, dbcsr_distribution_new, dbcsr_distribution_release, &
      30              :         dbcsr_distribution_type, dbcsr_finalize, dbcsr_put_block, dbcsr_release, dbcsr_type, &
      31              :         dbcsr_type_symmetric
      32              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_fm
      33              :    USE cp_files,                        ONLY: close_file,&
      34              :                                               discover_file,&
      35              :                                               open_file
      36              :    USE cp_fm_basic_linalg,              ONLY: cp_fm_column_scale,&
      37              :                                               cp_fm_syrk,&
      38              :                                               cp_fm_uplo_to_full
      39              :    USE cp_fm_diag,                      ONLY: cp_fm_geeig
      40              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      41              :                                               cp_fm_struct_release,&
      42              :                                               cp_fm_struct_type
      43              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      44              :                                               cp_fm_get_info,&
      45              :                                               cp_fm_get_submatrix,&
      46              :                                               cp_fm_release,&
      47              :                                               cp_fm_type
      48              :    USE generic_os_integrals,            ONLY: int_operators_r12_ab_os
      49              :    USE gw_auto_ri_types,                ONLY: auto_ri_type
      50              :    USE gw_utils_compute_integrals,      ONLY: build_3c_integral_block_ctx,&
      51              :                                               gw_3c_ctx_create,&
      52              :                                               gw_3c_ctx_release,&
      53              :                                               gw_3c_ctx_type,&
      54              :                                               gw_3c_ws_create,&
      55              :                                               gw_3c_ws_release,&
      56              :                                               gw_3c_ws_type
      57              :    USE ieee_arithmetic,                 ONLY: ieee_is_finite
      58              :    USE input_constants,                 ONLY: do_potential_coulomb,&
      59              :                                               do_potential_truncated,&
      60              :                                               tensor_small_cell_full_kp
      61              :    USE integral_library_types,          ONLY: active_integral_library,&
      62              :                                               library_libint
      63              :    USE kinds,                           ONLY: default_path_length,&
      64              :                                               dp
      65              :    USE libint_2c_3c,                    ONLY: libint_potential_type
      66              :    USE local_gemm_api,                  ONLY: LOCAL_GEMM_PU_HOST,&
      67              :                                               local_gemm_ctxt_type
      68              :    USE machine,                         ONLY: m_walltime
      69              :    USE mathlib,                         ONLY: diamat_all
      70              :    USE message_passing,                 ONLY: mp_para_env_type
      71              :    USE orbital_pointers,                ONLY: init_orbital_pointers,&
      72              :                                               ncoset
      73              :    USE orbital_transformation_matrices, ONLY: init_spherical_harmonics
      74              :    USE post_scf_bandstructure_types,    ONLY: post_scf_bandstructure_type
      75              :    USE qs_environment_types,            ONLY: get_qs_env,&
      76              :                                               qs_environment_type
      77              :    USE t_c_g0,                          ONLY: get_lmax_init,&
      78              :                                               init_t_c_g0 => init
      79              :    USE util,                            ONLY: sort
      80              : #include "base/base_uses.f90"
      81              : 
      82              :    IMPLICIT NONE
      83              :    PRIVATE
      84              : 
      85              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'gw_auto_ri_generate_RI_basis'
      86              : 
      87              :    ! Numerical cutoffs for the reference M_PQ eigenspace and final M_pq pseudoinverse.
      88              :    REAL(KIND=dp), PARAMETER :: reference_M_eigenval_threshold = 1.0E-10_dp
      89              :    REAL(KIND=dp), PARAMETER :: optimized_M_eigenval_threshold = 1.0E-6_dp
      90              :    ! Discard null target directions and reject non-positive-semidefinite M_PQ or B_PQ spectra.
      91              :    REAL(KIND=dp), PARAMETER :: relative_target_eigenval_threshold = 1.0E-12_dp
      92              :    REAL(KIND=dp), PARAMETER :: AB_absolute_target_eigenval_threshold = 1.0E-12_dp
      93              :    REAL(KIND=dp), PARAMETER :: negative_eigenval_tolerance = 1.0E-10_dp
      94              :    REAL(KIND=dp), PARAMETER :: conditioned_negative_eigenval_tolerance = SQRT(EPSILON(1.0_dp))
      95              : 
      96              :    TYPE AB_block_list_type
      97              :       INTEGER, ALLOCATABLE                               :: block(:)
      98              :       INTEGER, ALLOCATABLE                               :: first_p_AB(:)
      99              :       INTEGER, ALLOCATABLE                               :: first_p_atom(:)
     100              :       INTEGER, ALLOCATABLE                               :: size_opt_RI(:)
     101              :    END TYPE AB_block_list_type
     102              : 
     103              :    TYPE optimized_atom_U_type
     104              :       INTEGER, ALLOCATABLE                               :: ref_atom(:)
     105              :       INTEGER, ALLOCATABLE                               :: first_P(:)
     106              :       REAL(KIND=dp), ALLOCATABLE                         :: U_Pp(:, :)
     107              :    END TYPE optimized_atom_U_type
     108              : 
     109              :    PUBLIC :: generate_auto_ri_basis
     110              : 
     111              : CONTAINS
     112              : 
     113              : !**************************************************************************************************!
     114              : ! The full derivation of the AUTO_RI algorithm will be published separately.                       !
     115              : !                                                                                                  !
     116              : ! The equations below define the notation used throughout this automatic RI basis optimizer.       !
     117              : !                                                                                                  !
     118              : ! The RI scalar product uses the truncated Coulomb operator:                                       !
     119              : !                                                                                                  !
     120              : ! (f|g) = ∫dr∫dr' f(r) V_rc(r-r') g(r').                                                     (1)   !
     121              : !                                                                                                  !
     122              : ! For the RI basis-set optimization, we consider the orbital-product density formed by             !
     123              : ! AOs on atoms A and B,                                                                            !
     124              : !                                                                                                  !
     125              : ! ρ_mn^AB(r) = Σ_{μ∈A} Σ_{ν∈B} C_μm C_νn ϕ_μ(r) ϕ_ν(r).                                      (2)   !
     126              : !                                                                                                  !
     127              : ! The atom-local correlation target B_PQ^{A,C} mimics contributions to the density response χ,     !
     128              : ! and the exchange target B_PQ^{A,X} mimics contributions to the exchange self-energy:             !
     129              : !                                                                                                  !
     130              : ! B_PQ^{A,C} = (1/N_C^A) Σ_ia^oe w_ia (φ_P^A|ρ_ia^AA)(ρ_ia^AA|φ_Q^A).                        (3)   !
     131              : !                                                                                                  !
     132              : ! B_PQ^{A,X} = (1/N_X^A) Σ_{n∈W_occ} Σ_i^occ (φ_P^A|ρ_ni^AA)(ρ_ni^AA|φ_Q^A).                 (4)   !
     133              : !                                                                                                  !
     134              : ! W_occ = [ε_HOMO-E_win, ε_LUMO+E_win], where E_win is OCC_EMPTY_FRONTIER_ORBITAL_WINDOW.          !
     135              : !                                                                                                  !
     136              : ! Here w_ia gives larger weight to frontier-orbital products close to the HOMO and LUMO:           !
     137              : !                                                                                                  !
     138              : ! w_ia = 1/(|ε_i-E_F| |ε_a-E_F|),   E_F = (ε_HOMO+ε_LUMO)/2.                                 (5)   !
     139              : !                                                                                                  !
     140              : ! Diagonalizing B^A = B^{A,C}+B^{A,X} identifies the most important atom-local RI functions        !
     141              : ! that we use in the optimized RI basis set. The contraction coefficients U_Pp^A [see Eq. (7)]     !
     142              : ! used for optimized RI basis functions are obtained from the eigenvalue problem                   !
     143              : !                                                                                                  !
     144              : ! B^A U^A = M^A U^A λ^A,   Σ_Q B_PQ^A U_Qp^A = λ_p^A Σ_Q M_PQ^A U_Qp^A.                      (6)   !
     145              : !                                                                                                  !
     146              : ! The eigenvectors with the largest eigenvalues are the contracted RI functions φ_p^A(r)           !
     147              : ! used for the optimized RI basis set:                                                             !
     148              : !                                                                                                  !
     149              : ! φ_p^A(r) = Σ_P U_Pp^A φ_P^A(r).                                                            (7)   !
     150              : !                                                                                                  !
     151              : ! We also use superpositions of Gaussians on neighboring atoms in the RI basis. Before optimizing  !
     152              : ! the expansion coefficients, the AA from Eq. (7) used in AB contractions are projected out:       !
     153              : !                                                                                                  !
     154              : ! span{φ_P^{AB,⊥}} = span{φ_P^A,φ_Q^B} \ span{φ_p^A,φ_q^B}.                                  (8)   !
     155              : !                                                                                                  !
     156              : ! For obtaining the AB contraction coefficients, we use the symmetric off-diagonal density:        !
     157              : !                                                                                                  !
     158              : ! ρ̅_mn^AB(r) = ρ_mn^AB(r)+ρ_mn^BA(r).                                                        (9)   !
     159              : !                                                                                                  !
     160              : ! To identify the most important functions for a neighboring pair A-B, define                      !
     161              : ! correlation and exchange targets in this projected basis:                                        !
     162              : !                                                                                                  !
     163              : ! B_PQ^{AB,C} = (1/N_C^{AB}) Σ_ia^oe w_ia (φ_P^{AB,⊥}|ρ̅_ia^AB)(ρ̅_ia^AB|φ_Q^{AB,⊥}).         (10)   !
     164              : !                                                                                                  !
     165              : ! B_PQ^{AB,X} = (1/N_X^{AB}) Σ_{n∈W_occ} Σ_i^occ (φ_P^{AB,⊥}|ρ̅_ni^AB)(ρ̅_ni^AB|φ_Q^{AB,⊥}).  (11)   !
     166              : !                                                                                                  !
     167              : ! Diagonalizing their sum gives the AB-neighbor-pair contraction coefficients:                     !
     168              : !                                                                                                  !
     169              : ! B^AB U^AB = U^AB λ^AB,   B^AB = B^{AB,C}+B^{AB,X}.                                        (12)   !
     170              : !                                                                                                  !
     171              : ! The eigenvectors with the largest eigenvalues are used as AB contractions in the optimized       !
     172              : ! RI basis set:                                                                                    !
     173              : !                                                                                                  !
     174              : ! φ_p^AB(r) = Σ_P U_Pp^AB φ_P^{AB,⊥}(r).                                                    (13)   !
     175              : !                                                                                                  !
     176              : ! Eq. (13) means that the RI basis is not an atom-centered basis any more, but can be delocalized  !
     177              : ! over two neighboring atoms AB.                                                                   !
     178              : !                                                                                                  !
     179              : ! AUTO_RI uses the same number of AA and AB contractions in the final optimized RI basis:          !
     180              : !                                                                                                  !
     181              : ! Σ_A N_A = Σ_{A>B} N_AB = N_RI/2.                                                          (14)   !
     182              : !                                                                                                  !
     183              : ! If no atom pairs are available, as for an isolated atom or when NEIGHBOR_RADIUS is below the      !
     184              : ! minimum bond length, there is no AB space, so the full requested basis is assigned to AA:         !
     185              : !                                                                                                  !
     186              : ! Σ_A N_A^target = N_RI^target.                                                             (15)   !
     187              : !                                                                                                  !
     188              : ! The retained size can be smaller only when B^A or M^A has fewer numerically non-null modes.      !
     189              : !                                                                                                  !
     190              : !**************************************************************************************************!
     191              : 
     192              : ! **************************************************************************************************
     193              : !> \brief Executes the AUTO_RI algorithm defined by Eqs. (1)-(15).
     194              : !> \param qs_env ...
     195              : !> \param bs_env Band-structure environment containing GW parameters.
     196              : ! **************************************************************************************************
     197            4 :    SUBROUTINE generate_auto_ri_basis(qs_env, bs_env)
     198              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     199              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     200              : 
     201              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'generate_auto_ri_basis'
     202              : 
     203              :       INTEGER                                            :: handle
     204              :       REAL(KIND=dp)                                      :: time_start
     205            4 :       REAL(KIND=dp), ALLOCATABLE                         :: U_Pp_A(:, :, :)
     206              : 
     207            4 :       CALL timeset(routineN, handle)
     208              : 
     209            4 :       time_start = m_walltime()
     210              : 
     211              :       ! Compute AA target matrices B^{AA,C}, B^{AA,X} and contraction U_Pp^A in Eqs. (3)-(7).
     212            4 :       CALL compute_AA_optimized_RI_basis(bs_env, U_Pp_A)
     213              : 
     214              :       ! Compute AB target matrices B^{AB,C}, B^{AB,X} and contraction U_Pp^AB in Eqs. (8)-(13).
     215            4 :       CALL compute_AB_optimized_RI_basis(bs_env, U_Pp_A)
     216            4 :       DEALLOCATE (U_Pp_A)
     217              : 
     218              :       ! Compute and invert M_pq = (φ_p|φ_q) of the optimized RI basis in Eqs. (7) and (13).
     219            4 :       CALL compute_M_pq_inv_and_V_pq(qs_env, bs_env)
     220              : 
     221              :       ! Print the optimized RI basis dimension and total optimization time.
     222            4 :       CALL print_auto_ri_summary(bs_env, time_start)
     223              : 
     224            4 :       CALL timestop(handle)
     225              : 
     226            4 :    END SUBROUTINE generate_auto_ri_basis
     227              : 
     228              : ! **************************************************************************************************
     229              : !> \brief Computes the atom-local contraction coefficients U^A from Eqs. (3)-(7):
     230              : !>
     231              : !>          B_PQ^{A,C} = (1/N_C^A) Σ_ia^oe w_ia (φ_P^A|ρ_ia^AA)(ρ_ia^AA|φ_Q^A),         (3)
     232              : !>
     233              : !>          B_PQ^{A,X} = (1/N_X^A) Σ_{n∈W_occ} Σ_i^occ (φ_P^A|ρ_ni^AA)(ρ_ni^AA|φ_Q^A),  (4)
     234              : !>
     235              : !>          w_ia = 1/(|ε_i-E_F| |ε_a-E_F|),  E_F = (ε_HOMO+ε_LUMO)/2,                   (5)
     236              : !>
     237              : !>          B^A U^A = M^A U^A λ^A,                                                      (6)
     238              : !>
     239              : !>          Contracted RI basis functions for optimized RI basis:
     240              : !>
     241              : !>          φ_p^A(r) = Σ_P U_Pp^A φ_P^A(r).                                             (7)
     242              : !>
     243              : !> \param bs_env ...
     244              : !> \param U_Pp_A temporary atom-local contraction coefficients used to construct the AB space
     245              : ! **************************************************************************************************
     246            4 :    SUBROUTINE compute_AA_optimized_RI_basis(bs_env, U_Pp_A)
     247              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     248              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: U_Pp_A(:, :, :)
     249              : 
     250              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_AA_optimized_RI_basis'
     251              : 
     252              :       INTEGER :: handle, iatom, ikind, max_nAO_kind, max_nRI_kind, nAO_kind, natom, nkind, &
     253              :          nRI_kind, RI_size_opt_AA_requested, root_rank
     254            4 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: kind_of_atom, sizes_opt_RI_AA
     255              :       REAL(KIND=dp)                                      :: target_norm
     256            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenval
     257            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: B_PQ, eigenval_AA, M_PQ, U_Pp_A_atom
     258            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: M_PQ_by_kind
     259            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :, :)  :: Int_3c
     260              : 
     261            4 :       CALL timeset(routineN, handle)
     262              : 
     263              :       ! Determine reference dimensions and the number of AA RI functions (i.e. number of
     264              :       ! eigenvectors U_Pp^A used for RI-basis contractions).
     265              :       CALL initialize_AA_optimization(bs_env, kind_of_atom, natom, nkind, &
     266            4 :                                       max_nRI_kind, RI_size_opt_AA_requested)
     267           16 :       ALLOCATE (eigenval_AA(max_nRI_kind, natom), source=0.0_dp)
     268           12 :       ALLOCATE (sizes_opt_RI_AA(natom))
     269              : 
     270            4 :       CALL initialize_auto_ri_integrals(bs_env)
     271           16 :       max_nAO_kind = MAXVAL(bs_env%sizes_AO)
     272            0 :       ALLOCATE (U_Pp_A(max_nRI_kind, max_nRI_kind, natom), &
     273           36 :                 M_PQ_by_kind(max_nRI_kind, max_nRI_kind, nkind), source=0.0_dp)
     274           24 :       ALLOCATE (Int_3c(max_nAO_kind, max_nAO_kind, max_nRI_kind, nkind), source=0.0_dp)
     275              : 
     276              :       ! The few atom-kind integral blocks are inexpensive; compute them on every rank.
     277           12 :       DO ikind = 1, nkind
     278            8 :          nAO_kind = bs_env%basis_set_AO(ikind)%gto_basis_set%nsgf
     279            8 :          nRI_kind = bs_env%basis_set_RI(ikind)%gto_basis_set%nsgf
     280              :          CALL compute_int_3c( &
     281              :             ao_basis=bs_env%basis_set_AO(ikind)%gto_basis_set, &
     282              :             ri_basis=bs_env%basis_set_RI(ikind)%gto_basis_set, &
     283              :             potential=bs_env%ri_metric, M_PQ=M_PQ, &
     284            8 :             Int_3c=Int_3c(1:nAO_kind, 1:nAO_kind, 1:nRI_kind, ikind))
     285              :          CALL add_RI_metric_regularization(bs_env%basis_set_RI(ikind)%gto_basis_set, &
     286            8 :                                            bs_env%regularization_RI, M_PQ)
     287        78376 :          M_PQ_by_kind(1:nRI_kind, 1:nRI_kind, ikind) = M_PQ
     288           12 :          DEALLOCATE (M_PQ)
     289              :       END DO
     290              : 
     291           16 :       bs_env%auto_ri%sizes_opt_RI = 0
     292           16 :       DO iatom = 1, natom
     293           12 :          root_rank = MODULO(iatom - 1, bs_env%para_env%num_pe)
     294           12 :          ikind = kind_of_atom(iatom)
     295           12 :          nRI_kind = bs_env%basis_set_RI(ikind)%gto_basis_set%nsgf
     296           12 :          nAO_kind = bs_env%sizes_AO(iatom)
     297           48 :          ALLOCATE (M_PQ(nRI_kind, nRI_kind))
     298        90700 :          M_PQ(:, :) = M_PQ_by_kind(1:nRI_kind, 1:nRI_kind, ikind)
     299              :          ! Eqs. (3)-(5): form B^{A,C}, B^{A,X}, and their weighted sum B^A.
     300              :          CALL compute_B_PQ_AA(bs_env, iatom, root_rank, &
     301              :                               Int_3c(1:nAO_kind, 1:nAO_kind, 1:nRI_kind, ikind), &
     302        17204 :                               M_PQ, B_PQ, target_norm)
     303           12 :          DEALLOCATE (M_PQ)
     304           12 :          IF (root_rank /= bs_env%para_env%mepos) THEN
     305            6 :             DEALLOCATE (B_PQ)
     306            6 :             CYCLE
     307              :          END IF
     308              :          ! Eq. (6): solve B^A U^A = M^A U^A λ^A.
     309              :          CALL solve_AA_eigenproblem(M_PQ_by_kind(1:nRI_kind, 1:nRI_kind, ikind), &
     310            6 :                                     B_PQ, target_norm, U_Pp_A_atom, eigenval)
     311           40 :          eigenval_AA(1:SIZE(U_Pp_A_atom, 2), iatom) = eigenval(1:SIZE(U_Pp_A_atom, 2))
     312            6 :          bs_env%auto_ri%sizes_opt_RI(iatom) = SIZE(U_Pp_A_atom, 2)
     313         4100 :          U_Pp_A(1:SIZE(U_Pp_A_atom, 1), 1:SIZE(U_Pp_A_atom, 2), iatom) = U_Pp_A_atom
     314           10 :          DEALLOCATE (U_Pp_A_atom, eigenval, B_PQ)
     315              :       END DO
     316            4 :       CALL bs_env%para_env%sum(bs_env%auto_ri%sizes_opt_RI)
     317            4 :       CALL bs_env%para_env%sum(U_Pp_A)
     318            4 :       CALL bs_env%para_env%sum(eigenval_AA)
     319            4 :       CALL select_RI_sizes(eigenval_AA, RI_size_opt_AA_requested, 1, sizes_opt_RI_AA)
     320           16 :       bs_env%auto_ri%sizes_opt_RI(:) = sizes_opt_RI_AA
     321           16 :       DO iatom = 1, natom
     322       193000 :          U_Pp_A(:, sizes_opt_RI_AA(iatom) + 1:, iatom) = 0.0_dp
     323              :       END DO
     324            4 :       DEALLOCATE (Int_3c, M_PQ_by_kind)
     325              : 
     326            4 :       CALL timestop(handle)
     327              : 
     328            8 :    END SUBROUTINE compute_AA_optimized_RI_basis
     329              : 
     330              : ! **************************************************************************************************
     331              : !> \brief Initializes dimensions  for the AA optimization.
     332              : !>
     333              : !>        This routine maps every atom to its reference RI basis, allocates the persistent
     334              : !>        per-atom arrays, and determines the AA part of the requested basis size from
     335              : !>
     336              : !>          Σ_A N_A = N_RI/2.                                             (14)
     337              : !>
     338              : !>        If no atom pair lies within NEIGHBOR_RADIUS, so that no AB subspace
     339              : !>        exists, it instead assigns
     340              : !>
     341              : !>          Σ_A N_A^target = N_RI^target.                                 (15)
     342              : !>
     343              : !> \param bs_env ...
     344              : !> \param kind_of_atom ...
     345              : !> \param natom ...
     346              : !> \param nkind ...
     347              : !> \param max_nRI_kind ...
     348              : !> \param RI_size_opt_AA_requested ...
     349              : ! **************************************************************************************************
     350            4 :    SUBROUTINE initialize_AA_optimization(bs_env, kind_of_atom, &
     351              :                                          natom, nkind, max_nRI_kind, &
     352              :                                          RI_size_opt_AA_requested)
     353              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     354              :       INTEGER, ALLOCATABLE, INTENT(OUT)                  :: kind_of_atom(:)
     355              :       INTEGER, INTENT(OUT)                               :: natom, nkind, max_nRI_kind, &
     356              :                                                             RI_size_opt_AA_requested
     357              : 
     358              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'initialize_AA_optimization'
     359              : 
     360              :       INTEGER                                            :: AB_pair_count, handle, ikind, &
     361              :                                                             RI_size_requested
     362            4 :       INTEGER, ALLOCATABLE                               :: sizes_ref_RI_by_kind(:)
     363              : 
     364            4 :       CALL timeset(routineN, handle)
     365              : 
     366            4 :       natom = bs_env%n_atom
     367            4 :       CPASSERT(ASSOCIATED(bs_env%ri_rs%atomic_kind_set))
     368            4 :       CPASSERT(ASSOCIATED(bs_env%ri_rs%particle_set))
     369            4 :       CPASSERT(ASSOCIATED(bs_env%para_env) .AND. natom > 0)
     370            4 :       CPASSERT(ALLOCATED(bs_env%basis_set_AO))
     371            4 :       CPASSERT(ALLOCATED(bs_env%basis_set_RI))
     372            4 :       nkind = SIZE(bs_env%basis_set_RI)
     373            4 :       CPASSERT(nkind > 0)
     374            4 :       CPASSERT(SIZE(bs_env%basis_set_AO) == nkind)
     375            4 :       CPASSERT(bs_env%do_gw_ri_rs)
     376            4 :       CPASSERT(.NOT. bs_env%do_periodic)
     377            4 :       CPASSERT(bs_env%gw_implementation /= tensor_small_cell_full_kp)
     378              : 
     379              :       CALL get_atomic_kind_set(atomic_kind_set=bs_env%ri_rs%atomic_kind_set, &
     380            4 :                                kind_of=kind_of_atom)
     381           12 :       ALLOCATE (bs_env%auto_ri%sizes_opt_RI(natom))
     382           12 :       ALLOCATE (sizes_ref_RI_by_kind(nkind))
     383           12 :       DO ikind = 1, nkind
     384            8 :          CPASSERT(ASSOCIATED(bs_env%basis_set_AO(ikind)%gto_basis_set))
     385            8 :          CPASSERT(ASSOCIATED(bs_env%basis_set_RI(ikind)%gto_basis_set))
     386           12 :          sizes_ref_RI_by_kind(ikind) = bs_env%basis_set_RI(ikind)%gto_basis_set%nsgf
     387              :       END DO
     388           12 :       max_nRI_kind = MAXVAL(sizes_ref_RI_by_kind)
     389              :       RI_size_requested = NINT( &
     390              :                           MIN(bs_env%auto_ri%ri_ao_ratio, &
     391              :                               REAL(SUM(sizes_ref_RI_by_kind(kind_of_atom)), dp)/ &
     392           28 :                               REAL(SUM(bs_env%sizes_AO), dp))*REAL(SUM(bs_env%sizes_AO), dp))
     393            4 :       CALL count_AB_neighbor_pairs(bs_env, AB_pair_count)
     394            4 :       IF (AB_pair_count == 0) THEN
     395            2 :          RI_size_opt_AA_requested = RI_size_requested
     396              :       ELSE
     397            2 :          RI_size_opt_AA_requested = MAX(natom, NINT(0.5_dp*REAL(RI_size_requested, dp)))
     398              :       END IF
     399            4 :       DEALLOCATE (sizes_ref_RI_by_kind)
     400              : 
     401            4 :       CALL timestop(handle)
     402              : 
     403            8 :    END SUBROUTINE initialize_AA_optimization
     404              : 
     405              : ! **************************************************************************************************
     406              : !> \brief Counts atom pairs eligible for the AB contractions in Eqs. (8)-(13).
     407              : !> \param bs_env ...
     408              : !> \param AB_pair_count number of pairs separated by at most NEIGHBOR_RADIUS
     409              : ! **************************************************************************************************
     410            8 :    SUBROUTINE count_AB_neighbor_pairs(bs_env, AB_pair_count)
     411              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     412              :       INTEGER, INTENT(OUT)                               :: AB_pair_count
     413              : 
     414              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'count_AB_neighbor_pairs'
     415              : 
     416              :       INTEGER                                            :: handle, iatom, jatom
     417              : 
     418            8 :       CALL timeset(routineN, handle)
     419              : 
     420            8 :       AB_pair_count = 0
     421           24 :       DO iatom = 1, bs_env%n_atom - 1
     422           48 :          DO jatom = iatom + 1, bs_env%n_atom
     423           96 :             IF (NORM2(bs_env%ri_rs%particle_set(iatom)%r - bs_env%ri_rs%particle_set(jatom)%r) <= &
     424           16 :                 bs_env%auto_ri%neighbor_radius) THEN
     425           12 :                AB_pair_count = AB_pair_count + 1
     426              :             END IF
     427              :          END DO
     428              :       END DO
     429              : 
     430            8 :       CALL timestop(handle)
     431              : 
     432            8 :    END SUBROUTINE count_AB_neighbor_pairs
     433              : 
     434              : ! **************************************************************************************************
     435              : !> \brief Sets the RI-RS block sizes to the optimized RI basis dimensions.
     436              : !> \param bs_env ...
     437              : ! **************************************************************************************************
     438            4 :    SUBROUTINE set_optimized_RI_basis_sizes(bs_env)
     439              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     440              : 
     441              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'set_optimized_RI_basis_sizes'
     442              : 
     443              :       INTEGER                                            :: handle
     444              : 
     445            4 :       CALL timeset(routineN, handle)
     446              : 
     447           16 :       bs_env%sizes_RI(:) = bs_env%auto_ri%sizes_opt_RI
     448            4 :       bs_env%auto_ri%ready = .TRUE.
     449              : 
     450            4 :       CALL timestop(handle)
     451              : 
     452            4 :    END SUBROUTINE set_optimized_RI_basis_sizes
     453              : 
     454              : ! **************************************************************************************************
     455              : !> \brief Prints the optimized RI basis size and total optimization time.
     456              : !> \param bs_env ...
     457              : !> \param time_start ...
     458              : ! **************************************************************************************************
     459            4 :    SUBROUTINE print_auto_ri_summary(bs_env, time_start)
     460              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     461              :       REAL(KIND=dp), INTENT(IN)                          :: time_start
     462              : 
     463              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_auto_ri_summary'
     464              : 
     465              :       INTEGER                                            :: handle, RI_size_optimized, unit_nr
     466              :       REAL(KIND=dp)                                      :: elapsed
     467              : 
     468            4 :       CALL timeset(routineN, handle)
     469              : 
     470           16 :       RI_size_optimized = SUM(bs_env%auto_ri%sizes_opt_RI)
     471            4 :       elapsed = m_walltime() - time_start
     472            4 :       CALL bs_env%para_env%max(elapsed)
     473            4 :       unit_nr = bs_env%unit_nr
     474            4 :       IF (unit_nr > 0) THEN
     475            2 :          WRITE (unit_nr, '(A)') ' '
     476              :          WRITE (unit_nr, '(T2,A,T74,I9)') &
     477            2 :             'AUTO_RI| Number of automatic RI functions for χ, ε, W:', RI_size_optimized
     478              :          WRITE (unit_nr, '(T2,A,T71,F8.1,A)') &
     479            2 :             'AUTO_RI| Total RI basis optimization time:', elapsed, ' s'
     480              :       END IF
     481              : 
     482            4 :       CALL timestop(handle)
     483              : 
     484            4 :    END SUBROUTINE print_auto_ri_summary
     485              : 
     486              : ! **************************************************************************************************
     487              : !> \brief Adds REGULARIZATION_RI to a two-center RI metric matrix (P|Q).
     488              : !> \param basis ...
     489              : !> \param regularization ...
     490              : !> \param M_PQ reference RI metric M_PQ
     491              : !> \param offset ...
     492              : ! **************************************************************************************************
     493           14 :    SUBROUTINE add_RI_metric_regularization(basis, regularization, M_PQ, offset)
     494              :       USE basis_set_types, ONLY: gto_basis_set_type
     495              :       TYPE(gto_basis_set_type), INTENT(IN)               :: basis
     496              :       REAL(KIND=dp), INTENT(IN)                          :: regularization
     497              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: M_PQ
     498              :       INTEGER, INTENT(IN), OPTIONAL                      :: offset
     499              : 
     500              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'add_RI_metric_regularization'
     501              : 
     502              :       INTEGER                                            :: first, handle, i, iset, nset, start
     503           14 :       INTEGER, DIMENSION(:), POINTER                     :: npgf, nsgf_set
     504           14 :       INTEGER, DIMENSION(:, :), POINTER                  :: first_sgf
     505              :       REAL(KIND=dp)                                      :: shift
     506           14 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: zet
     507              : 
     508           14 :       CALL timeset(routineN, handle)
     509              : 
     510           14 :       start = 0
     511           14 :       IF (PRESENT(offset)) start = offset
     512              :       CALL get_gto_basis_set(basis, first_sgf=first_sgf, npgf=npgf, nset=nset, &
     513           14 :                              nsgf_set=nsgf_set, zet=zet)
     514           14 :       CPASSERT(start >= 0 .AND. start + basis%nsgf <= SIZE(M_PQ, 1))
     515           14 :       CPASSERT(start + basis%nsgf <= SIZE(M_PQ, 2))
     516          302 :       DO iset = 1, nset
     517          576 :          shift = regularization*MAX(1.0_dp, 1.0_dp/MINVAL(zet(1:npgf(iset), iset)))
     518          288 :          first = start + first_sgf(1, iset)
     519         1510 :          DO i = first, first + nsgf_set(iset) - 1
     520         1496 :             M_PQ(i, i) = M_PQ(i, i) + shift
     521              :          END DO
     522              :       END DO
     523              : 
     524           14 :       CALL timestop(handle)
     525              : 
     526           14 :    END SUBROUTINE add_RI_metric_regularization
     527              : 
     528              : ! **************************************************************************************************
     529              : !> \brief Copies atom-local diagonal blocks M^AA from a distributed metric M.
     530              : !> \param M_pq_local locally stored entries of the optimized RI metric M_pq
     531              : !> \param M_pq_AA_local locally stored entries of the atom-block-diagonal metric
     532              : !> \param row_indices global indices of the locally owned rows
     533              : !> \param col_indices global indices of the locally owned columns
     534              : !> \param sizes_RI optimized RI functions in each atomic matrix block
     535              : ! **************************************************************************************************
     536            4 :    SUBROUTINE copy_M_pq_AA_blocks(M_pq_local, M_pq_AA_local, row_indices, &
     537            4 :                                   col_indices, sizes_RI)
     538              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: M_pq_local
     539              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: M_pq_AA_local
     540              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: row_indices, col_indices, sizes_RI
     541              : 
     542              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'copy_M_pq_AA_blocks'
     543              : 
     544              :       INTEGER                                            :: handle, i, j, last_index
     545            4 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: block_ends, col_atom, row_atom
     546              : 
     547            4 :       CALL timeset(routineN, handle)
     548              : 
     549            4 :       CPASSERT(SIZE(M_pq_local, 1) == SIZE(row_indices))
     550            4 :       CPASSERT(SIZE(M_pq_local, 2) == SIZE(col_indices))
     551           12 :       CPASSERT(ALL(SHAPE(M_pq_AA_local) == SHAPE(M_pq_local)))
     552           16 :       CPASSERT(SIZE(sizes_RI) > 0 .AND. ALL(sizes_RI > 0))
     553              : 
     554           12 :       ALLOCATE (block_ends(SIZE(sizes_RI)))
     555            4 :       block_ends(1) = sizes_RI(1)
     556           12 :       DO i = 2, SIZE(sizes_RI)
     557           12 :          block_ends(i) = block_ends(i - 1) + sizes_RI(i)
     558              :       END DO
     559            4 :       last_index = block_ends(SIZE(block_ends))
     560           33 :       CPASSERT(ALL(row_indices >= 1 .AND. row_indices <= last_index))
     561           62 :       CPASSERT(ALL(col_indices >= 1 .AND. col_indices <= last_index))
     562              : 
     563           20 :       ALLOCATE (row_atom(SIZE(row_indices)), col_atom(SIZE(col_indices)))
     564           33 :       DO i = 1, SIZE(row_indices)
     565           33 :          row_atom(i) = M_pq_index_atom(row_indices(i), block_ends)
     566              :       END DO
     567           62 :       DO j = 1, SIZE(col_indices)
     568           62 :          col_atom(j) = M_pq_index_atom(col_indices(j), block_ends)
     569              :       END DO
     570              : 
     571          507 :       M_pq_AA_local = 0.0_dp
     572           62 :       DO j = 1, SIZE(col_indices)
     573          507 :          DO i = 1, SIZE(row_indices)
     574          503 :             IF (row_atom(i) == col_atom(j)) THEN
     575          193 :                M_pq_AA_local(i, j) = M_pq_local(i, j)
     576              :             END IF
     577              :          END DO
     578              :       END DO
     579              : 
     580            4 :       CALL timestop(handle)
     581              : 
     582            8 :    END SUBROUTINE copy_M_pq_AA_blocks
     583              : 
     584              : ! **************************************************************************************************
     585              : !> \brief Forms f_i=1/sqrt(λ_i) for eigenvalues of the RI metric matrix M_PQ.
     586              : !> \param eigenval generalized metric eigenvalues
     587              : !> \param factors inverse-square-root weights, or zero for discarded modes
     588              : ! **************************************************************************************************
     589            4 :    SUBROUTINE compute_optimized_M_pq_inv_factors(eigenval, factors)
     590              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: eigenval
     591              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     592              :          INTENT(OUT)                                     :: factors
     593              : 
     594              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_optimized_M_pq_inv_factors'
     595              : 
     596              :       INTEGER                                            :: handle, i, n_dependent
     597              :       REAL(KIND=dp)                                      :: largest, threshold
     598              : 
     599            4 :       CALL timeset(routineN, handle)
     600              : 
     601            4 :       CPASSERT(SIZE(eigenval) > 0)
     602              : 
     603           62 :       largest = MAXVAL(eigenval)
     604            4 :       CPASSERT(largest > 0.0_dp)
     605            4 :       threshold = optimized_M_eigenval_threshold*largest
     606           12 :       ALLOCATE (factors(SIZE(eigenval)))
     607            4 :       factors = 0.0_dp
     608            4 :       n_dependent = 0
     609           62 :       DO i = 1, SIZE(eigenval)
     610           62 :          IF (eigenval(i) > 0.0_dp .AND. eigenval(i) >= threshold) THEN
     611           58 :             factors(i) = 1.0_dp/SQRT(eigenval(i))
     612              :          ELSE
     613            0 :             n_dependent = n_dependent + 1
     614              :          END IF
     615              :       END DO
     616            4 :       CPASSERT(n_dependent < SIZE(eigenval))
     617              : 
     618            4 :       CALL timestop(handle)
     619              : 
     620            4 :    END SUBROUTINE compute_optimized_M_pq_inv_factors
     621              : 
     622              : ! **************************************************************************************************
     623              : !> \brief Finds the atom owning a global RI metric index.
     624              : !> \param global_index global metric index
     625              : !> \param block_ends inclusive end index of each consecutive atom block
     626              : !> \return atom index
     627              : ! **************************************************************************************************
     628           87 :    PURE FUNCTION M_pq_index_atom(global_index, block_ends) RESULT(atom_index)
     629              :       INTEGER, INTENT(IN)                                :: global_index
     630              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: block_ends
     631              :       INTEGER                                            :: atom_index
     632              : 
     633              :       INTEGER                                            :: high, low, middle
     634              : 
     635           87 :       low = 1
     636           87 :       high = SIZE(block_ends)
     637          243 :       DO WHILE (low < high)
     638          156 :          middle = (low + high)/2
     639          243 :          IF (global_index <= block_ends(middle)) THEN
     640              :             high = middle
     641              :          ELSE
     642           39 :             low = middle + 1
     643              :          END IF
     644              :       END DO
     645           87 :       atom_index = low
     646              : 
     647           87 :    END FUNCTION M_pq_index_atom
     648              : 
     649              : ! **************************************************************************************************
     650              : !> \brief Computes X=U diag(1/sqrt(λ)) with X^T M X=I.
     651              : !>
     652              : !>        The columns of X span the non-null eigenspace of the symmetric
     653              : !>        two-center RI metric matrix M=U diag(λ) U^T.
     654              : !> \param M_PQ ...
     655              : !> \param X_Pi ...
     656              : !> \param rank ...
     657              : ! **************************************************************************************************
     658           21 :    SUBROUTINE compute_reference_M_PQ_inv_factor(M_PQ, X_Pi, rank)
     659              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: M_PQ
     660              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
     661              :          INTENT(OUT)                                     :: X_Pi
     662              :       INTEGER, INTENT(OUT)                               :: rank
     663              : 
     664              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_reference_M_PQ_inv_factor'
     665              : 
     666              :       INTEGER                                            :: column, handle, i, n
     667              :       REAL(KIND=dp)                                      :: largest
     668           21 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenval
     669           21 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: eigenvec
     670              : 
     671           21 :       CALL timeset(routineN, handle)
     672              : 
     673           21 :       n = SIZE(M_PQ, 1)
     674           21 :       CPASSERT(n > 0 .AND. SIZE(M_PQ, 2) == n)
     675       499976 :       ALLOCATE (eigenvec, source=0.5_dp*(M_PQ + TRANSPOSE(M_PQ)))
     676           63 :       ALLOCATE (eigenval(n))
     677           21 :       CALL diamat_all(eigenvec, eigenval, dac=.TRUE.)
     678         1943 :       largest = MAXVAL(eigenval)
     679           21 :       IF (largest <= 0.0_dp) CPABORT("AUTO_RI metric has no positive non-null range")
     680         1943 :       IF (MINVAL(eigenval) < -negative_eigenval_tolerance*largest) THEN
     681            0 :          CPABORT("AUTO_RI metric has no positive non-null range")
     682              :       END IF
     683         1943 :       rank = COUNT(eigenval > reference_M_eigenval_threshold*largest)
     684           21 :       IF (rank == 0) CPABORT("AUTO_RI metric has no positive non-null range")
     685           84 :       ALLOCATE (X_Pi(n, rank))
     686           21 :       column = 0
     687         1943 :       DO i = 1, n
     688         1922 :          IF (eigenval(i) <= reference_M_eigenval_threshold*largest) CYCLE
     689         1922 :          column = column + 1
     690       249967 :          X_Pi(:, column) = eigenvec(:, i)/SQRT(eigenval(i))
     691              :       END DO
     692              : 
     693           21 :       CALL timestop(handle)
     694              : 
     695           42 :    END SUBROUTINE compute_reference_M_PQ_inv_factor
     696              : 
     697              : ! **************************************************************************************************
     698              : !> \brief Solves eigenvalue problem B^A U^A=M^A U^A λ^A in Eq. (6).
     699              : !>
     700              : !>        Columns are ordered from the largest to the smallest λ^A.
     701              : !>        Columns are retained when λ_i/λ_max >= eigenval_threshold.
     702              : !> \param M_PQ metric of the large reference RI basis
     703              : !> \param B_PQ normalized atom-local target matrix B^A
     704              : !> \param target_normalization ...
     705              : !> \param U_Pp_A selected AA contractions from the large RI basis P to optimized functions p
     706              : !> \param eigenval normalized target eigenvalues in descending order
     707              : ! **************************************************************************************************
     708            6 :    SUBROUTINE solve_AA_eigenproblem(M_PQ, B_PQ, target_normalization, U_Pp_A, eigenval)
     709              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: M_PQ, B_PQ
     710              :       REAL(KIND=dp), INTENT(IN)                          :: target_normalization
     711              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
     712              :          INTENT(OUT)                                     :: U_Pp_A
     713              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     714              :          INTENT(OUT)                                     :: eigenval
     715              : 
     716              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'solve_AA_eigenproblem'
     717              : 
     718              :       INTEGER                                            :: handle, i, j, M_PQ_rank, n, nret
     719              :       REAL(KIND=dp)                                      :: largest_score, relative_fitting_defect
     720            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenval_B, eigenval_M
     721            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: B_ij, M_pp_check, U_ij, U_Pp_A_rotated, &
     722            6 :                                                             X_Pi
     723              : 
     724            6 :       CALL timeset(routineN, handle)
     725              : 
     726            6 :       n = SIZE(M_PQ, 1)
     727            6 :       CPASSERT(n > 0)
     728            6 :       CPASSERT(SIZE(M_PQ, 2) == n)
     729           18 :       CPASSERT(ALL(SHAPE(B_PQ) == [n, n]))
     730            6 :       CPASSERT(target_normalization > 0.0_dp)
     731            6 :       CALL compute_reference_M_PQ_inv_factor(M_PQ, X_Pi, M_PQ_rank)
     732              : 
     733           24 :       ALLOCATE (B_ij(M_PQ_rank, M_PQ_rank))
     734            6 :       B_ij(:, :) = MATMUL(TRANSPOSE(X_Pi), &
     735        90712 :                           MATMUL(0.5_dp*(B_PQ + TRANSPOSE(B_PQ)), X_Pi))/target_normalization
     736        45368 :       ALLOCATE (U_ij, source=B_ij)
     737           18 :       ALLOCATE (eigenval_B(M_PQ_rank))
     738            6 :       CALL diamat_all(U_ij, eigenval_B, dac=.TRUE.)
     739          482 :       largest_score = MAXVAL(eigenval_B)
     740            6 :       IF (largest_score <= 0.0_dp) THEN
     741            0 :          CPABORT("AUTO_RI conditioned atom-local eigensolver failed")
     742              :       END IF
     743          482 :       IF (MINVAL(eigenval_B) < -conditioned_negative_eigenval_tolerance*largest_score) THEN
     744            0 :          CPABORT("AUTO_RI conditioned atom-local eigensolver failed")
     745              :       END IF
     746          482 :       eigenval_B(:) = MAX(eigenval_B, 0.0_dp)
     747          482 :       nret = COUNT(eigenval_B/largest_score >= relative_target_eigenval_threshold)
     748           30 :       ALLOCATE (U_Pp_A(n, nret), eigenval(M_PQ_rank))
     749          482 :       DO i = 1, M_PQ_rank
     750          482 :          eigenval(i) = eigenval_B(M_PQ_rank - i + 1)
     751              :       END DO
     752           40 :       DO i = 1, nret
     753           34 :          j = M_PQ_rank - i + 1
     754      1519020 :          U_Pp_A(:, i) = MATMUL(X_Pi, U_ij(:, j))
     755              :       END DO
     756              : 
     757              :       relative_fitting_defect = 1.0_dp
     758           40 :       DO i = 1, nret
     759           34 :          j = M_PQ_rank - i + 1
     760              :          relative_fitting_defect = relative_fitting_defect - &
     761       511848 :                                    DOT_PRODUCT(U_ij(:, j), MATMUL(B_ij, U_ij(:, j)))
     762              :       END DO
     763            6 :       IF (ABS(relative_fitting_defect) < 100.0_dp*EPSILON(1.0_dp)) THEN
     764              :          relative_fitting_defect = 0.0_dp
     765              :       END IF
     766              :       ! A projection cannot capture more than the exact product norm.
     767            6 :       IF (relative_fitting_defect < -1.0E-8_dp .OR. &
     768              :           relative_fitting_defect > 1.0_dp + 1.0E-8_dp) THEN
     769            0 :          CPABORT("AUTO_RI conditioned atom-local eigensolver failed")
     770              :       END IF
     771            6 :       IF (nret > 0) THEN
     772           48 :          ALLOCATE (M_pp_check(nret, nret), eigenval_M(nret), U_Pp_A_rotated(n, nret))
     773            6 :          M_pp_check(:, :) = MATMUL(TRANSPOSE(U_Pp_A), &
     774        70682 :                                    MATMUL(0.5_dp*(M_PQ + TRANSPOSE(M_PQ)), U_Pp_A))
     775            6 :          CALL diamat_all(M_pp_check, eigenval_M, dac=.TRUE.)
     776           40 :          IF (MINVAL(eigenval_M) <= reference_M_eigenval_threshold) THEN
     777            0 :             CPABORT("AUTO_RI conditioned atom-local eigensolver failed")
     778              :          END IF
     779          894 :          U_Pp_A_rotated(:, :) = MATMUL(U_Pp_A, M_pp_check)
     780           40 :          DO i = 1, nret
     781         4100 :             U_Pp_A_rotated(:, i) = U_Pp_A_rotated(:, i)/SQRT(eigenval_M(i))
     782              :          END DO
     783          894 :          U_Pp_A(:, :) = MATMUL(U_Pp_A_rotated, TRANSPOSE(M_pp_check))
     784            6 :          M_pp_check(:, :) = MATMUL(TRANSPOSE(U_Pp_A), &
     785        71122 :                                    MATMUL(0.5_dp*(M_PQ + TRANSPOSE(M_PQ)), U_Pp_A))
     786           40 :          DO i = 1, nret
     787           40 :             M_pp_check(i, i) = M_pp_check(i, i) - 1.0_dp
     788              :          END DO
     789          494 :          IF (MAXVAL(ABS(M_pp_check)) >= 1.0E-7_dp) THEN
     790            0 :             CPABORT("AUTO_RI conditioned atom-local eigensolver failed")
     791              :          END IF
     792              :       END IF
     793              : 
     794            6 :       CALL timestop(handle)
     795              : 
     796           12 :    END SUBROUTINE solve_AA_eigenproblem
     797              : 
     798              : ! **************************************************************************************************
     799              : !> \brief Solves eigenvalue problem B^AB U^AB=U^AB λ^AB of Eq. (8).
     800              : !>
     801              : !>        Columns are ordered from the largest to the smallest λ^AB.
     802              : !> \param M_PQ combined AB reference-basis metric
     803              : !> \param B_PQ normalized two-center target matrix B^AB
     804              : !> \param U_Pp_AA retained AA columns embedded in the combined AB reference space
     805              : !> \param U_Pp_AB selected AB contractions from the large RI basis P to optimized functions p
     806              : !> \param eigenval normalized scores in descending order
     807              : ! **************************************************************************************************
     808            3 :    SUBROUTINE solve_AB_eigenproblem(M_PQ, B_PQ, U_Pp_AA, U_Pp_AB, eigenval)
     809              :       REAL(KIND=dp), INTENT(IN)                          :: M_PQ(:, :), B_PQ(:, :), U_Pp_AA(:, :)
     810              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: U_Pp_AB(:, :), eigenval(:)
     811              : 
     812              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'solve_AB_eigenproblem'
     813              : 
     814              :       INTEGER                                            :: handle, i, k, n, nkeep, rank
     815              :       REAL(KIND=dp)                                      :: threshold
     816            3 :       REAL(KIND=dp), ALLOCATABLE :: B_ij(:, :), B_ij_residual(:, :), eigenval_residual(:), &
     817            3 :          M_AA(:, :), P_ij(:, :), Q_ia(:, :), U_Aa(:, :), U_ia(:, :), X_Pi(:, :)
     818              : 
     819            3 :       CALL timeset(routineN, handle)
     820              : 
     821            3 :       n = SIZE(M_PQ, 1)
     822            3 :       CALL compute_reference_M_PQ_inv_factor(M_PQ, X_Pi, rank)
     823              : 
     824              :       ! Xᵀ M X=I; Q spans the retained atomic columns in these orthonormal coordinates.
     825           12 :       ALLOCATE (Q_ia(rank, SIZE(U_Pp_AA, 2)))
     826       121560 :       Q_ia(:, :) = MATMUL(TRANSPOSE(X_Pi), MATMUL(M_PQ, U_Pp_AA))
     827           12 :       ALLOCATE (P_ij(rank, rank), source=0.0_dp)
     828          479 :       DO i = 1, rank
     829          479 :          P_ij(i, i) = 1.0_dp
     830              :       END DO
     831            3 :       IF (SIZE(U_Pp_AA, 2) > 0) THEN
     832           12 :          ALLOCATE (M_AA(SIZE(U_Pp_AA, 2), SIZE(U_Pp_AA, 2)))
     833        24017 :          M_AA(:, :) = MATMUL(TRANSPOSE(Q_ia), Q_ia)
     834            3 :          CALL compute_reference_M_PQ_inv_factor(M_AA, U_Aa, k)
     835           12 :          ALLOCATE (U_ia(rank, SIZE(U_Aa, 2)))
     836        47881 :          U_ia(:, :) = MATMUL(Q_ia, U_Aa)
     837       643217 :          P_ij(:, :) = P_ij - MATMUL(U_ia, TRANSPOSE(U_ia))
     838            3 :          DEALLOCATE (U_ia)
     839              :       END IF
     840            9 :       ALLOCATE (B_ij(rank, rank))
     841            6 :       B_ij(:, :) = MATMUL(TRANSPOSE(X_Pi), MATMUL(B_PQ, X_Pi))
     842            9 :       ALLOCATE (B_ij_residual(rank, rank))
     843            6 :       B_ij_residual(:, :) = MATMUL(P_ij, MATMUL(B_ij, P_ij))
     844            9 :       ALLOCATE (eigenval_residual(rank))
     845            3 :       CALL diamat_all(B_ij_residual, eigenval_residual, dac=.TRUE.)
     846              :       threshold = MAX(AB_absolute_target_eigenval_threshold, &
     847              :                       relative_target_eigenval_threshold*MAXVAL(eigenval_residual), &
     848        80036 :                       100.0_dp*EPSILON(1.0_dp)*MAXVAL(ABS(B_ij)))
     849          479 :       nkeep = COUNT(eigenval_residual > threshold)
     850           18 :       ALLOCATE (U_Pp_AB(n, nkeep), eigenval(nkeep))
     851            3 :       k = 0
     852          479 :       DO i = rank, 1, -1
     853          476 :          IF (eigenval_residual(i) <= threshold) CYCLE
     854           14 :          k = k + 1
     855      1349880 :          U_Pp_AB(:, k) = MATMUL(X_Pi, MATMUL(P_ij, B_ij_residual(:, i)))
     856           17 :          eigenval(k) = eigenval_residual(i)
     857              :       END DO
     858              : 
     859            3 :       CALL timestop(handle)
     860              : 
     861            6 :    END SUBROUTINE solve_AB_eigenproblem
     862              : 
     863              : ! **************************************************************************************************
     864              : !> \brief Selects the requested RI sizes from the largest positive eigenvalues.
     865              : !> \param eigenval_by_block descending eigenvalues; zero pads unavailable modes
     866              : !> \param RI_size_requested requested total number of functions
     867              : !> \param RI_size_minimum minimum retained size in every nonempty block
     868              : !> \param sizes_RI_selected number retained from each block
     869              : ! **************************************************************************************************
     870            8 :    SUBROUTINE select_RI_sizes(eigenval_by_block, RI_size_requested, RI_size_minimum, &
     871            8 :                               sizes_RI_selected)
     872              :       REAL(KIND=dp), INTENT(IN)                          :: eigenval_by_block(:, :)
     873              :       INTEGER, INTENT(IN)                                :: RI_size_requested, RI_size_minimum
     874              :       INTEGER, INTENT(OUT)                               :: sizes_RI_selected(:)
     875              : 
     876              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'select_RI_sizes'
     877              : 
     878              :       INTEGER                                            :: block, handle, i, n, nmode, remaining
     879            8 :       INTEGER, ALLOCATABLE                               :: indices(:)
     880            8 :       REAL(KIND=dp), ALLOCATABLE                         :: values(:)
     881              : 
     882            8 :       CALL timeset(routineN, handle)
     883              : 
     884            8 :       nmode = SIZE(eigenval_by_block, 1)
     885           24 :       n = SIZE(eigenval_by_block)
     886            8 :       CPASSERT(SIZE(sizes_RI_selected) == SIZE(eigenval_by_block, 2))
     887            8 :       CPASSERT(RI_size_minimum >= 0)
     888           26 :       sizes_RI_selected(:) = 0
     889            8 :       IF (n > 0) THEN
     890           30 :          ALLOCATE (values(n), indices(n))
     891         3084 :          values(:) = -RESHAPE(eigenval_by_block, [n])
     892           24 :          DO block = 1, SIZE(sizes_RI_selected)
     893              :             sizes_RI_selected(block) = &
     894         3090 :                MIN(RI_size_minimum, COUNT(eigenval_by_block(:, block) > 0.0_dp))
     895              :             values((block - 1)*nmode + 1: &
     896           36 :                    (block - 1)*nmode + sizes_RI_selected(block)) = 0.0_dp
     897              :          END DO
     898            6 :          CALL sort(values, n, indices)
     899         3096 :          remaining = MIN(MAX(0, RI_size_requested - SUM(sizes_RI_selected)), COUNT(values < 0.0_dp))
     900           52 :          DO i = 1, remaining
     901           46 :             block = (indices(i) - 1)/nmode + 1
     902           52 :             sizes_RI_selected(block) = sizes_RI_selected(block) + 1
     903              :          END DO
     904              :       END IF
     905              : 
     906            8 :       CALL timestop(handle)
     907              : 
     908            8 :    END SUBROUTINE select_RI_sizes
     909              : 
     910              : ! **************************************************************************************************
     911              : !> \brief Computes orbital factors for the occupied-empty weight in Eq. (5).
     912              : !>
     913              : !>        weights(n) = 1/|ε_n-E_F|, so weights(i)*weights(a) = w_ia.
     914              : !>
     915              : !> \param eigenval_MO molecular-orbital eigenvalues, occupied first
     916              : !> \param homo Last occupied state in this spin channel.
     917              : !> \param weights factors whose occupied-empty products give w_ia
     918              : ! **************************************************************************************************
     919           15 :    SUBROUTINE compute_orbital_weights(eigenval_MO, homo, weights)
     920              :       REAL(KIND=dp), INTENT(IN)                          :: eigenval_MO(:)
     921              :       INTEGER, INTENT(IN)                                :: homo
     922              :       REAL(KIND=dp), INTENT(OUT)                         :: weights(:)
     923              : 
     924              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_orbital_weights'
     925              : 
     926              :       INTEGER                                            :: handle, n
     927              :       REAL(KIND=dp)                                      :: gap, middle
     928              : 
     929           15 :       CALL timeset(routineN, handle)
     930              : 
     931           15 :       CPASSERT(SIZE(weights) == SIZE(eigenval_MO))
     932           15 :       IF (homo < 1 .OR. homo >= SIZE(eigenval_MO)) THEN
     933            0 :          CPABORT("AUTO_RI orbital weights require occupied and empty states")
     934              :       END IF
     935          120 :       IF (.NOT. ALL(ieee_is_finite(eigenval_MO))) THEN
     936            0 :          CPABORT("AUTO_RI orbital weights require finite eigenvalues")
     937              :       END IF
     938           15 :       gap = eigenval_MO(homo + 1) - eigenval_MO(homo)
     939           15 :       IF (.NOT. ieee_is_finite(gap) .OR. gap <= 0.0_dp) THEN
     940            0 :          CPABORT("AUTO_RI orbital weights require a finite positive gap")
     941              :       END IF
     942           15 :       middle = 0.5_dp*(eigenval_MO(homo) + eigenval_MO(homo + 1))
     943          120 :       DO n = 1, SIZE(eigenval_MO)
     944          120 :          weights(n) = 1.0_dp/ABS(eigenval_MO(n) - middle)
     945              :       END DO
     946              : 
     947           15 :       CALL timestop(handle)
     948              : 
     949           15 :    END SUBROUTINE compute_orbital_weights
     950              : 
     951              : ! **************************************************************************************************
     952              : !> \brief Computes the occupied-empty contribution B_PQ^C in Eqs. (3) or (10).
     953              : !>
     954              : !>        B_PQ^C = Σ_ia w_ia (P|ρ_ia)(ρ_ia|Q).
     955              : !>
     956              : !> \param Int_3c atom-local or atom-pair three-center integrals (μν|P)
     957              : !> \param C_mu_n molecular-orbital coefficients restricted to the relevant AO rows
     958              : !> \param weights Per-orbital weights for this spin channel.
     959              : !> \param homo Last occupied state.
     960              : !> \param B_PQ unnormalized transition matrix for this spin channel
     961              : ! **************************************************************************************************
     962           12 :    SUBROUTINE compute_B_PQ_transition(Int_3c, C_mu_n, weights, homo, B_PQ)
     963              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: Int_3c(:, :, :), C_mu_n(:, :), weights(:)
     964              :       INTEGER, INTENT(IN)                                :: homo
     965              :       REAL(KIND=dp), INTENT(OUT)                         :: B_PQ(:, :)
     966              : 
     967              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_B_PQ_transition'
     968              : 
     969              :       INTEGER                                            :: handle, n, nAO, nRI, nstates
     970           12 :       REAL(KIND=dp), ALLOCATABLE                         :: empty(:, :), occupied(:, :), scaled(:, :)
     971              :       TYPE(local_gemm_ctxt_type)                         :: gemm_context
     972              : 
     973           12 :       CALL timeset(routineN, handle)
     974              : 
     975           12 :       CALL gemm_context%create(LOCAL_GEMM_PU_HOST)
     976           12 :       nAO = SIZE(C_mu_n, 1)
     977           12 :       nstates = SIZE(C_mu_n, 2)
     978           12 :       nRI = SIZE(Int_3c, 3)
     979           12 :       CPASSERT(SIZE(Int_3c, 1) == nAO .AND. SIZE(Int_3c, 2) == nAO)
     980           12 :       CPASSERT(SIZE(B_PQ, 1) == nRI .AND. SIZE(B_PQ, 2) == nRI)
     981           12 :       CPASSERT(SIZE(weights) == nstates .AND. homo > 0 .AND. homo < nstates)
     982          108 :       ALLOCATE (scaled(nAO, nstates), occupied(nAO, nAO), empty(nAO, nAO))
     983           96 :       DO n = 1, nstates
     984          390 :          scaled(:, n) = C_mu_n(:, n)*SQRT(weights(n))
     985              :       END DO
     986              :       CALL gemm_context%gemm( &
     987              :          'N', 'T', nAO, nAO, homo, 1.0_dp, scaled, nAO, scaled, nAO, &
     988           12 :          0.0_dp, occupied, nAO)
     989              :       CALL gemm_context%gemm('N', 'T', nAO, nAO, nstates - homo, 1.0_dp, &
     990              :                              scaled(:, homo + 1:), nAO, scaled(:, homo + 1:), nAO, &
     991           12 :                              0.0_dp, empty, nAO)
     992           12 :       CALL contract_B_PQ(Int_3c, occupied, empty, B_PQ)
     993           12 :       CALL gemm_context%destroy()
     994              : 
     995           12 :       CALL timestop(handle)
     996              : 
     997           24 :    END SUBROUTINE compute_B_PQ_transition
     998              : 
     999              : ! **************************************************************************************************
    1000              : !> \brief Computes the frontier-exchange contribution B_PQ^X in Eqs. (4) or (11).
    1001              : !>
    1002              : !>        B_PQ^X = Σ_{n∈W_occ} Σ_i^occ (P|ρ_ni)(ρ_ni|Q).
    1003              : !>
    1004              : !> \param Int_3c atom-local or atom-pair three-center integrals (μν|P)
    1005              : !> \param C_mu_n molecular-orbital coefficients restricted to the relevant AO rows
    1006              : !> \param eigenval_MO molecular-orbital eigenvalues, occupied first, in Hartree
    1007              : !> \param homo Last occupied state in this spin channel.
    1008              : !> \param occ_energy_window frontier-window extension in Hartree
    1009              : !> \param B_PQ unnormalized exchange matrix
    1010              : ! **************************************************************************************************
    1011           12 :    SUBROUTINE compute_B_PQ_exchange(Int_3c, C_mu_n, eigenval_MO, homo, occ_energy_window, B_PQ)
    1012              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: Int_3c(:, :, :), C_mu_n(:, :), &
    1013              :                                                             eigenval_MO(:)
    1014              :       INTEGER, INTENT(IN)                                :: homo
    1015              :       REAL(KIND=dp), INTENT(IN)                          :: occ_energy_window
    1016              :       REAL(KIND=dp), INTENT(OUT)                         :: B_PQ(:, :)
    1017              : 
    1018              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_B_PQ_exchange'
    1019              : 
    1020              :       INTEGER                                            :: handle, n, nAO, nstates
    1021              :       REAL(KIND=dp), ALLOCATABLE                         :: frontier(:, :), occupied(:, :), &
    1022           12 :                                                             selected(:, :)
    1023              :       TYPE(local_gemm_ctxt_type)                         :: gemm_context
    1024              : 
    1025           12 :       CALL timeset(routineN, handle)
    1026              : 
    1027       204464 :       B_PQ(:, :) = 0.0_dp
    1028           12 :       nstates = SIZE(eigenval_MO)
    1029           12 :       IF (homo < 1 .OR. homo >= nstates) THEN
    1030            0 :          CPABORT("AUTO_RI exchange target requires occupied and empty states")
    1031              :       END IF
    1032           96 :       IF (.NOT. ieee_is_finite(occ_energy_window) .OR. &
    1033              :           .NOT. ALL(ieee_is_finite(eigenval_MO))) THEN
    1034            0 :          CPABORT("AUTO_RI exchange target requires finite input")
    1035              :       END IF
    1036           12 :       IF (occ_energy_window < 0.0_dp .OR. eigenval_MO(homo + 1) <= eigenval_MO(homo)) THEN
    1037            0 :          CPABORT("AUTO_RI exchange target requires a valid frontier window")
    1038              :       END IF
    1039           12 :       nAO = SIZE(C_mu_n, 1)
    1040           12 :       CPASSERT(SIZE(C_mu_n, 2) == nstates)
    1041          108 :       ALLOCATE (occupied(nAO, nAO), frontier(nAO, nAO), selected(nAO, nstates), source=0.0_dp)
    1042           96 :       DO n = 1, nstates
    1043           84 :          IF (eigenval_MO(n) < eigenval_MO(homo) - occ_energy_window .OR. &
    1044              :              eigenval_MO(n) > eigenval_MO(homo + 1) + occ_energy_window) CYCLE
    1045          240 :          selected(:, n) = C_mu_n(:, n)
    1046              :       END DO
    1047           12 :       CALL gemm_context%create(LOCAL_GEMM_PU_HOST)
    1048              :       ! D_μν = Σ_i^occ C_μi C_νi.
    1049              :       ! F_μν = Σ_{n: ε_n ∈ [ε_H−E_win,ε_L+E_win]} C_μn C_νn.
    1050              :       CALL gemm_context%gemm('N', 'T', nAO, nAO, homo, 1.0_dp, C_mu_n, nAO, C_mu_n, nAO, &
    1051           12 :                              0.0_dp, occupied, nAO)
    1052              :       CALL gemm_context%gemm('N', 'T', nAO, nAO, nstates, 1.0_dp, selected, nAO, selected, nAO, &
    1053           12 :                              0.0_dp, frontier, nAO)
    1054           12 :       CALL contract_B_PQ(Int_3c, occupied, frontier, B_PQ)
    1055           12 :       CALL gemm_context%destroy()
    1056              : 
    1057           12 :       CALL timestop(handle)
    1058              : 
    1059           24 :    END SUBROUTINE compute_B_PQ_exchange
    1060              : 
    1061              : ! **************************************************************************************************
    1062              : !> \brief Contracts AO density matrices and (μν|P) to form B_PQ.
    1063              : !>
    1064              : !>        B_PQ = Σ_μν (μν|P) [left (..|Q) right]_μν.
    1065              : !>
    1066              : !> \param Int_3c atom-local or atom-pair three-center integrals (μν|P)
    1067              : !> \param left First density block, for occupied partners.
    1068              : !> \param right Second density block, for empty or hard-window target states.
    1069              : !> \param B_PQ symmetric RI-basis product matrix
    1070              : ! **************************************************************************************************
    1071           24 :    SUBROUTINE contract_B_PQ(Int_3c, left, right, B_PQ)
    1072              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: Int_3c(:, :, :), left(:, :), right(:, :)
    1073              :       REAL(KIND=dp), INTENT(OUT)                         :: B_PQ(:, :)
    1074              : 
    1075              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'contract_B_PQ'
    1076              : 
    1077              :       INTEGER                                            :: b, handle, mu, naux, nu, p, q
    1078           24 :       REAL(KIND=dp), ALLOCATABLE                         :: weighted(:, :), work(:, :)
    1079              :       TYPE(local_gemm_ctxt_type)                         :: gemm_context
    1080              : 
    1081           24 :       CALL timeset(routineN, handle)
    1082              : 
    1083           24 :       b = SIZE(Int_3c, 1)
    1084           24 :       naux = SIZE(Int_3c, 3)
    1085           24 :       CPASSERT(SIZE(Int_3c, 2) == b)
    1086           24 :       CPASSERT(SIZE(left, 1) == b .AND. SIZE(left, 2) == b)
    1087           24 :       CPASSERT(SIZE(right, 1) == b .AND. SIZE(right, 2) == b)
    1088           24 :       CPASSERT(SIZE(B_PQ, 1) == naux .AND. SIZE(B_PQ, 2) == naux)
    1089          144 :       ALLOCATE (work(b, b), weighted(b, b))
    1090           24 :       CALL gemm_context%create(LOCAL_GEMM_PU_HOST)
    1091         2880 :       DO q = 1, naux
    1092              :          ! B_PQ = Σ_μν (μν|P) [left (..|Q) right]_μν.
    1093              :          CALL gemm_context%gemm('N', 'N', b, b, b, 1.0_dp, left, b, Int_3c(:, :, q), b, &
    1094         2856 :                                 0.0_dp, work, b)
    1095         2856 :          CALL gemm_context%gemm('N', 'N', b, b, b, 1.0_dp, work, b, right, b, 0.0_dp, weighted, b)
    1096       207332 :          DO p = 1, q
    1097       204452 :             B_PQ(p, q) = 0.0_dp
    1098      1238860 :             DO nu = 1, b
    1099      7023228 :                DO mu = 1, b
    1100      6818776 :                   B_PQ(p, q) = B_PQ(p, q) + Int_3c(mu, nu, p)*weighted(mu, nu)
    1101              :                END DO
    1102              :             END DO
    1103       207308 :             B_PQ(q, p) = B_PQ(p, q)
    1104              :          END DO
    1105              :       END DO
    1106           24 :       CALL gemm_context%destroy()
    1107              : 
    1108           24 :       CALL timestop(handle)
    1109              : 
    1110           48 :    END SUBROUTINE contract_B_PQ
    1111              : 
    1112              : ! **************************************************************************************************
    1113              : !> \brief Adds separately normalized transition and exchange matrices.
    1114              : !> \param X_Pi inverse metric factor, X_Pi X_Qi = M_PQ^+
    1115              : !> \param B_PQ_transition transition matrix, replaced by the combined target
    1116              : !> \param B_PQ_exchange unnormalized hard-window exchange covariance
    1117              : !> \param target_norm number of nonzero normalized terms
    1118              : ! **************************************************************************************************
    1119            6 :    SUBROUTINE combine_transition_and_exchange_targets( &
    1120            6 :       X_Pi, B_PQ_transition, B_PQ_exchange, target_norm)
    1121              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: X_Pi(:, :)
    1122              :       REAL(KIND=dp), INTENT(INOUT)                       :: B_PQ_transition(:, :)
    1123              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: B_PQ_exchange(:, :)
    1124              :       REAL(KIND=dp), INTENT(OUT)                         :: target_norm
    1125              : 
    1126              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'combine_transition_and_exchange_targets'
    1127              : 
    1128              :       INTEGER                                            :: handle, k
    1129              :       REAL(KIND=dp)                                      :: exchange_norm, transition_norm
    1130            6 :       REAL(KIND=dp), ALLOCATABLE                         :: work(:, :)
    1131              : 
    1132            6 :       CALL timeset(routineN, handle)
    1133              : 
    1134           24 :       ALLOCATE (work(SIZE(X_Pi, 1), SIZE(X_Pi, 2)))
    1135            6 :       work(:, :) = MATMUL(B_PQ_transition, X_Pi)
    1136            6 :       transition_norm = 0.0_dp
    1137          482 :       DO k = 1, SIZE(X_Pi, 2)
    1138        45350 :          transition_norm = transition_norm + DOT_PRODUCT(X_Pi(:, k), work(:, k))
    1139              :       END DO
    1140            6 :       work(:, :) = MATMUL(B_PQ_exchange, X_Pi)
    1141            6 :       exchange_norm = 0.0_dp
    1142          482 :       DO k = 1, SIZE(X_Pi, 2)
    1143        45350 :          exchange_norm = exchange_norm + DOT_PRODUCT(X_Pi(:, k), work(:, k))
    1144              :       END DO
    1145            6 :       target_norm = 0.0_dp
    1146              :       IF (.NOT. ieee_is_finite(transition_norm) .OR. &
    1147              :           .NOT. ieee_is_finite(exchange_norm) .OR. &
    1148            6 :           transition_norm < 0.0_dp .OR. exchange_norm < 0.0_dp) THEN
    1149            0 :          CPABORT("AUTO_RI target normalization is invalid")
    1150              :       END IF
    1151              :       ! B = B_ia / Tr(M^+ B_ia) + B_ni / Tr(M^+ B_ni), with zero-norm terms omitted.
    1152            6 :       IF (transition_norm > 0.0_dp) THEN
    1153        45350 :          B_PQ_transition(:, :) = B_PQ_transition/transition_norm
    1154            6 :          target_norm = target_norm + 1.0_dp
    1155              :       ELSE
    1156            0 :          B_PQ_transition(:, :) = 0.0_dp
    1157              :       END IF
    1158            6 :       IF (exchange_norm > 0.0_dp) THEN
    1159        45350 :          B_PQ_transition(:, :) = B_PQ_transition + B_PQ_exchange/exchange_norm
    1160            6 :          target_norm = target_norm + 1.0_dp
    1161              :       END IF
    1162        45350 :       IF (target_norm <= 0.0_dp .OR. .NOT. ALL(ieee_is_finite(B_PQ_transition))) THEN
    1163            0 :          CPABORT("AUTO_RI target normalization is invalid")
    1164              :       END IF
    1165              : 
    1166            6 :       CALL timestop(handle)
    1167              : 
    1168           12 :    END SUBROUTINE combine_transition_and_exchange_targets
    1169              : 
    1170              : ! **************************************************************************************************
    1171              : !> \brief Computes the atom-local matrix B_PQ^A in Eqs. (3)-(6).
    1172              : !>
    1173              : !>        B_PQ^A = B_PQ^{A,C}/N_C^A + B_PQ^{A,X}/N_X^A.
    1174              : !>
    1175              : !>        The molecular-orbital coefficient rows belonging to one atom are
    1176              : !>        collected on one MPI rank. No global AO-pair matrix is formed.
    1177              : !> \param bs_env ...
    1178              : !> \param iatom Atom whose AO rows are needed.
    1179              : !> \param root_rank MPI rank performing the atomic contractions.
    1180              : !> \param Int_3c atom-local three-center integrals (μν|P)
    1181              : !> \param M_PQ atom-local metric of the large reference RI basis
    1182              : !> \param B_PQ normalized atom-local matrix B_PQ^A
    1183              : !> \param target_norm number of nonzero normalized contributions
    1184              : ! **************************************************************************************************
    1185           12 :    SUBROUTINE compute_B_PQ_AA(bs_env, iatom, root_rank, Int_3c, M_PQ, &
    1186              :                               B_PQ, target_norm)
    1187              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1188              :       INTEGER, INTENT(IN)                                :: iatom, root_rank
    1189              :       REAL(KIND=dp), CONTIGUOUS, INTENT(IN)              :: Int_3c(:, :, :), M_PQ(:, :)
    1190              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: B_PQ(:, :)
    1191              :       REAL(KIND=dp), INTENT(OUT)                         :: target_norm
    1192              : 
    1193              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_B_PQ_AA'
    1194              : 
    1195              :       INTEGER                                            :: column, first, handle, i, j, nAO, nRI, &
    1196              :                                                             nstates, rank, row, spin
    1197           12 :       INTEGER, POINTER                                   :: cols(:), rows(:)
    1198              :       REAL(KIND=dp), ALLOCATABLE                         :: B_PQ_exchange(:, :), B_PQ_spin(:, :), &
    1199           12 :                                                             C_mu_n(:, :), weights(:), X_Pi(:, :)
    1200              : 
    1201           12 :       CALL timeset(routineN, handle)
    1202           12 :       NULLIFY (rows, cols)
    1203           12 :       nAO = SIZE(Int_3c, 1)
    1204           12 :       nRI = SIZE(Int_3c, 3)
    1205           12 :       nstates = bs_env%n_mo_retained
    1206           12 :       CPASSERT(ALLOCATED(bs_env%eigenval_scf_Gamma))
    1207           24 :       first = 1 + SUM(bs_env%sizes_AO(:iatom - 1))
    1208           48 :       ALLOCATE (B_PQ(nRI, nRI), source=0.0_dp)
    1209           96 :       ALLOCATE (C_mu_n(nAO, nstates), weights(nstates), B_PQ_spin(nRI, nRI))
    1210           12 :       target_norm = 0.0_dp
    1211           36 :       ALLOCATE (B_PQ_exchange(nRI, nRI), source=0.0_dp)
    1212           24 :       DO spin = 1, bs_env%n_spin
    1213              :          CALL compute_orbital_weights(bs_env%eigenval_scf_Gamma(1:nstates, spin), &
    1214           12 :                                       bs_env%n_occ(spin), weights)
    1215           12 :          CALL cp_fm_get_info(bs_env%fm_mo_coeff_Gamma(spin), row_indices=rows, col_indices=cols)
    1216           12 :          C_mu_n(:, :) = 0.0_dp
    1217           54 :          DO i = 1, SIZE(rows)
    1218           42 :             row = rows(i) - first + 1
    1219           42 :             IF (row < 1 .OR. row > nAO) CYCLE
    1220          124 :             DO j = 1, SIZE(cols)
    1221           98 :                column = cols(j)
    1222           98 :                IF (column > nstates) CYCLE
    1223          140 :                C_mu_n(row, column) = bs_env%fm_mo_coeff_Gamma(spin)%local_data(i, j)
    1224              :             END DO
    1225              :          END DO
    1226           12 :          CALL bs_env%para_env%sum(C_mu_n, root_rank)
    1227           12 :          IF (bs_env%para_env%mepos /= root_rank) CYCLE
    1228            6 :          CALL compute_B_PQ_transition(Int_3c, C_mu_n, weights, bs_env%n_occ(spin), B_PQ_spin)
    1229        45350 :          B_PQ(:, :) = B_PQ + B_PQ_spin
    1230              :          CALL compute_B_PQ_exchange(Int_3c, C_mu_n, &
    1231              :                                     bs_env%eigenval_scf_Gamma(1:nstates, spin), &
    1232            6 :                                     bs_env%n_occ(spin), bs_env%auto_ri%occ_energy_window, B_PQ_spin)
    1233        45368 :          B_PQ_exchange(:, :) = B_PQ_exchange + B_PQ_spin
    1234              :       END DO
    1235           12 :       IF (bs_env%para_env%mepos /= root_rank) THEN
    1236            6 :          CALL timestop(handle)
    1237            6 :          RETURN
    1238              :       END IF
    1239            6 :       CALL compute_reference_M_PQ_inv_factor(M_PQ, X_Pi, rank)
    1240            6 :       CALL combine_transition_and_exchange_targets(X_Pi, B_PQ, B_PQ_exchange, target_norm)
    1241            6 :       CALL timestop(handle)
    1242              : 
    1243           30 :    END SUBROUTINE compute_B_PQ_AA
    1244              : 
    1245              : ! **************************************************************************************************
    1246              : !> \brief Normalizes each contraction coefficient vector U_Pp.
    1247              : !>
    1248              : !>        U_Pp <- U_Pp/sqrt(Σ_P |U_Pp|^2).
    1249              : !>
    1250              : !> \param U_Pp contractions from the large RI basis P to optimized functions p
    1251              : ! **************************************************************************************************
    1252           18 :    SUBROUTINE normalize_RI_coefficients(U_Pp)
    1253              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: U_Pp
    1254              : 
    1255              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'normalize_RI_coefficients'
    1256              : 
    1257              :       INTEGER                                            :: handle, j
    1258              :       REAL(KIND=dp)                                      :: column_norm
    1259              : 
    1260           18 :       CALL timeset(routineN, handle)
    1261              : 
    1262           76 :       DO j = 1, SIZE(U_Pp, 2)
    1263         7742 :          column_norm = NORM2(U_Pp(:, j))
    1264           58 :          IF (column_norm <= TINY(1.0_dp)) THEN
    1265            0 :             CPABORT("AUTO_RI produced a numerically zero localized column")
    1266              :          END IF
    1267         7760 :          U_Pp(:, j) = U_Pp(:, j)/column_norm
    1268              :       END DO
    1269              : 
    1270           18 :       CALL timestop(handle)
    1271              : 
    1272           18 :    END SUBROUTINE normalize_RI_coefficients
    1273              : 
    1274              : ! **************************************************************************************************
    1275              : !> \brief Creates a matrix in the existing molecular BLACS context.
    1276              : !> \param qs_env ...
    1277              : !> \param matrix ...
    1278              : !> \param nr ...
    1279              : !> \param nc ...
    1280              : ! **************************************************************************************************
    1281           24 :    SUBROUTINE create_distributed_matrix(qs_env, matrix, nr, nc)
    1282              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    1283              :       TYPE(cp_fm_type), INTENT(OUT)                      :: matrix
    1284              :       INTEGER, INTENT(IN)                                :: nr, nc
    1285              : 
    1286              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'create_distributed_matrix'
    1287              : 
    1288              :       INTEGER                                            :: handle
    1289              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs
    1290              :       TYPE(cp_fm_struct_type), POINTER                   :: layout
    1291              :       TYPE(mp_para_env_type), POINTER                    :: para
    1292              : 
    1293           24 :       CALL timeset(routineN, handle)
    1294              : 
    1295           24 :       NULLIFY (blacs, para, layout)
    1296           24 :       CALL get_qs_env(qs_env, blacs_env=blacs, para_env=para)
    1297           24 :       CALL cp_fm_struct_create(layout, context=blacs, nrow_global=nr, ncol_global=nc, para_env=para)
    1298           24 :       CALL cp_fm_create(matrix, layout, set_zero=.TRUE.)
    1299           24 :       CALL cp_fm_struct_release(layout)
    1300              : 
    1301           24 :       CALL timestop(handle)
    1302              : 
    1303           24 :    END SUBROUTINE create_distributed_matrix
    1304              : 
    1305              : ! **************************************************************************************************
    1306              : !> \brief Computes and stores the two-center contractions U^AB in Eqs. (8)-(13).
    1307              : !>
    1308              : !>        The selected AA space is projected out according to Eq. (8). The symmetric
    1309              : !>        pair density in Eq. (9) defines B^{AB,C} and B^{AB,X} in Eqs. (10) and (11).
    1310              : !>        Diagonalizing their sum gives U^AB in Eq. (12), which expands the contracted
    1311              : !>        pair functions in Eq. (13).
    1312              : !> \param bs_env ...
    1313              : !> \param U_Pp_A temporary atom-local contractions used to project the AA space
    1314              : ! **************************************************************************************************
    1315            4 :    SUBROUTINE compute_AB_optimized_RI_basis(bs_env, U_Pp_A)
    1316              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1317              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: U_Pp_A
    1318              : 
    1319              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_AB_optimized_RI_basis'
    1320              : 
    1321              :       INTEGER :: AB_pair_count, center_index, current_atom, expected_RI_size, first_AB_pair, &
    1322              :          handle, handle_pair_batches, iatom, jatom, k, last_AB_pair, nAO_kind, nRI_A, nRI_AB_ref, &
    1323              :          nRI_B, RI_size_opt_AA, RI_size_opt_AB, RI_size_optimized, RI_size_ref, RI_size_requested, &
    1324              :          root_rank, row, size_opt_RI_AB, size_U_Pp_AB, size_U_Pp_AB_storage, spin
    1325            4 :       INTEGER, ALLOCATABLE :: AB_atom_A(:), AB_atom_B(:), AB_size_opt_RI_to_A(:), block_next(:), &
    1326            4 :          sizes_opt_RI_AA(:), sizes_opt_RI_AB(:), sizes_ref_RI(:), U_Pp_AB_offset(:)
    1327            4 :       REAL(KIND=dp), ALLOCATABLE :: B_PQ(:, :), C_mu_n_AB(:, :, :), C_mu_n_atom(:, :), &
    1328            4 :          eigenval(:), eigenval_AB(:, :), M_PQ(:, :), U_Pp_AA(:, :), U_Pp_AB(:, :), &
    1329            4 :          U_Pp_AB_packed(:)
    1330           52 :       TYPE(gw_3c_ctx_type)                               :: ctx
    1331            4 :       TYPE(gw_3c_ws_type)                                :: ws
    1332              :       TYPE(mp_para_env_type), POINTER                    :: para
    1333              : 
    1334            4 :       CALL timeset(routineN, handle)
    1335              : 
    1336            4 :       para => bs_env%para_env
    1337           16 :       ALLOCATE (sizes_ref_RI(bs_env%n_atom), sizes_opt_RI_AA(bs_env%n_atom))
    1338           16 :       DO iatom = 1, bs_env%n_atom
    1339           16 :          sizes_ref_RI(iatom) = get_ref_RI_size(bs_env, iatom)
    1340              :       END DO
    1341           16 :       sizes_opt_RI_AA(:) = bs_env%auto_ri%sizes_opt_RI
    1342           16 :       RI_size_ref = SUM(sizes_ref_RI)
    1343           16 :       RI_size_opt_AA = SUM(sizes_opt_RI_AA)
    1344              : 
    1345            4 :       CALL count_AB_neighbor_pairs(bs_env, AB_pair_count)
    1346              :       ALLOCATE (AB_atom_A(AB_pair_count), AB_atom_B(AB_pair_count), &
    1347              :                 AB_size_opt_RI_to_A(AB_pair_count), sizes_opt_RI_AB(AB_pair_count), &
    1348           18 :                 U_Pp_AB_offset(AB_pair_count))
    1349            4 :       AB_size_opt_RI_to_A = 0
    1350            4 :       U_Pp_AB_offset = 0
    1351           26 :       ALLOCATE (eigenval_AB(2*MAXVAL(sizes_ref_RI), AB_pair_count), source=0.0_dp)
    1352              : 
    1353              :       CALL gw_3c_ctx_create(ctx, bs_env, bs_env%ri_metric, &
    1354              :                             basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
    1355            4 :                             basis_i=bs_env%basis_set_RI)
    1356            4 :       CALL gw_3c_ws_create(ws, ctx)
    1357            4 :       k = 0
    1358           12 :       DO iatom = 1, bs_env%n_atom - 1
    1359           24 :          DO jatom = iatom + 1, bs_env%n_atom
    1360           48 :             IF (NORM2(bs_env%ri_rs%particle_set(iatom)%r - bs_env%ri_rs%particle_set(jatom)%r) > &
    1361              :                 bs_env%auto_ri%neighbor_radius) CYCLE
    1362            6 :             k = k + 1
    1363            6 :             AB_atom_A(k) = iatom
    1364           20 :             AB_atom_B(k) = jatom
    1365              :          END DO
    1366              :       END DO
    1367              :       size_U_Pp_AB_storage = 0
    1368           10 :       DO k = 1, AB_pair_count
    1369            6 :          root_rank = MODULO(k - 1, para%num_pe)
    1370            6 :          IF (para%mepos /= root_rank) CYCLE
    1371            3 :          nRI_AB_ref = sizes_ref_RI(AB_atom_A(k)) + sizes_ref_RI(AB_atom_B(k))
    1372            3 :          U_Pp_AB_offset(k) = size_U_Pp_AB_storage + 1
    1373           10 :          size_U_Pp_AB_storage = size_U_Pp_AB_storage + nRI_AB_ref*nRI_AB_ref
    1374              :       END DO
    1375           10 :       ALLOCATE (U_Pp_AB_packed(size_U_Pp_AB_storage))
    1376            4 :       CALL timeset(routineN//'_pair_batches', handle_pair_batches)
    1377            8 :       DO first_AB_pair = 1, AB_pair_count, para%num_pe
    1378            4 :          last_AB_pair = MIN(AB_pair_count, first_AB_pair + para%num_pe - 1)
    1379              :          ! Gather only one pair's MO rows per rank, then solve all batch pairs concurrently.
    1380           10 :          DO k = first_AB_pair, last_AB_pair
    1381            6 :             root_rank = k - first_AB_pair
    1382            6 :             iatom = AB_atom_A(k)
    1383            6 :             jatom = AB_atom_B(k)
    1384            6 :             IF (para%mepos == root_rank) THEN
    1385            0 :                ALLOCATE (C_mu_n_AB( &
    1386              :                          bs_env%sizes_AO(iatom) + bs_env%sizes_AO(jatom), &
    1387           15 :                          bs_env%n_mo_retained, bs_env%n_spin))
    1388              :             END IF
    1389           16 :             DO spin = 1, bs_env%n_spin
    1390              :                row = 0
    1391           24 :                DO center_index = 1, 2
    1392           12 :                   current_atom = iatom
    1393           12 :                   IF (center_index == 2) current_atom = jatom
    1394           12 :                   nAO_kind = bs_env%sizes_AO(current_atom)
    1395           48 :                   ALLOCATE (C_mu_n_atom(nAO_kind, bs_env%n_mo_retained))
    1396              :                   CALL cp_fm_get_submatrix(bs_env%fm_mo_coeff_Gamma(spin), C_mu_n_atom, &
    1397           24 :                                            start_row=1 + SUM(bs_env%sizes_AO(:current_atom - 1)))
    1398           12 :                   IF (para%mepos == root_rank) THEN
    1399          146 :                      C_mu_n_AB(row + 1:row + nAO_kind, :, spin) = C_mu_n_atom
    1400              :                   END IF
    1401           12 :                   DEALLOCATE (C_mu_n_atom)
    1402           18 :                   row = row + nAO_kind
    1403              :                END DO
    1404              :             END DO
    1405              :          END DO
    1406            4 :          k = first_AB_pair + para%mepos
    1407            8 :          IF (k <= last_AB_pair) THEN
    1408            3 :             iatom = AB_atom_A(k)
    1409            3 :             jatom = AB_atom_B(k)
    1410            3 :             nRI_A = sizes_ref_RI(iatom)
    1411            3 :             nRI_B = sizes_ref_RI(jatom)
    1412            3 :             nRI_AB_ref = nRI_A + nRI_B
    1413              :             ! Eqs. (9)-(11): form the symmetric pair density and B^{AB,C}+B^{AB,X}.
    1414            3 :             CALL compute_B_PQ_AB(bs_env, ctx, ws, iatom, jatom, C_mu_n_AB, M_PQ, B_PQ)
    1415            0 :             ALLOCATE (U_Pp_AA(nRI_AB_ref, sizes_opt_RI_AA(iatom) + sizes_opt_RI_AA(jatom)), &
    1416           12 :                       source=0.0_dp)
    1417              :             U_Pp_AA(1:nRI_A, 1:sizes_opt_RI_AA(iatom)) = &
    1418         1865 :                U_Pp_A(1:nRI_A, 1:sizes_opt_RI_AA(iatom), iatom)
    1419              :             U_Pp_AA(nRI_A + 1:, sizes_opt_RI_AA(iatom) + 1:) = &
    1420          171 :                U_Pp_A(1:nRI_B, 1:sizes_opt_RI_AA(jatom), jatom)
    1421              :             ! Eqs. (8) and (12): project out the AA space and diagonalize the remainder.
    1422            3 :             CALL solve_AB_eigenproblem(M_PQ, B_PQ, U_Pp_AA, U_Pp_AB, eigenval)
    1423           17 :             eigenval_AB(1:SIZE(eigenval), k) = eigenval
    1424            3 :             DEALLOCATE (eigenval)
    1425              :             U_Pp_AB_packed(U_Pp_AB_offset(k):U_Pp_AB_offset(k) + SIZE(U_Pp_AB) - 1) = &
    1426           18 :                RESHAPE(U_Pp_AB, [SIZE(U_Pp_AB)])
    1427            3 :             DEALLOCATE (U_Pp_AB)
    1428            3 :             DEALLOCATE (U_Pp_AA, B_PQ, M_PQ, C_mu_n_AB)
    1429              :          END IF
    1430              :       END DO
    1431            4 :       CALL timestop(handle_pair_batches)
    1432            4 :       k = AB_pair_count
    1433            4 :       CALL gw_3c_ws_release(ws)
    1434            4 :       CALL gw_3c_ctx_release(ctx)
    1435              :       RI_size_requested = NINT(MIN(bs_env%auto_ri%ri_ao_ratio, &
    1436              :                                    REAL(RI_size_ref, dp)/REAL(SUM(bs_env%sizes_AO), dp))* &
    1437           16 :                                REAL(SUM(bs_env%sizes_AO), dp))
    1438            4 :       RI_size_requested = MAX(RI_size_requested, bs_env%n_atom)
    1439              : 
    1440            4 :       CALL para%sum(eigenval_AB)
    1441              :       CALL select_RI_sizes(eigenval_AB, RI_size_requested - SUM(sizes_opt_RI_AA), 0, &
    1442           16 :                            sizes_opt_RI_AB)
    1443           10 :       RI_size_opt_AB = SUM(sizes_opt_RI_AB)
    1444           16 :       bs_env%auto_ri%sizes_opt_RI(:) = sizes_opt_RI_AA
    1445           10 :       DO k = 1, AB_pair_count
    1446            6 :          iatom = AB_atom_A(k)
    1447            6 :          jatom = AB_atom_B(k)
    1448            6 :          size_opt_RI_AB = sizes_opt_RI_AB(k)
    1449              :          AB_size_opt_RI_to_A(k) = balance_AB_columns(bs_env%auto_ri%sizes_opt_RI(iatom), &
    1450              :                                                      bs_env%auto_ri%sizes_opt_RI(jatom), &
    1451            6 :                                                      size_opt_RI_AB)
    1452              :          bs_env%auto_ri%sizes_opt_RI(iatom) = &
    1453            6 :             bs_env%auto_ri%sizes_opt_RI(iatom) + AB_size_opt_RI_to_A(k)
    1454              :          bs_env%auto_ri%sizes_opt_RI(jatom) = &
    1455           10 :             bs_env%auto_ri%sizes_opt_RI(jatom) + size_opt_RI_AB - AB_size_opt_RI_to_A(k)
    1456              :       END DO
    1457              : 
    1458           16 :       RI_size_optimized = SUM(bs_env%auto_ri%sizes_opt_RI)
    1459            4 :       expected_RI_size = RI_size_opt_AA + RI_size_opt_AB
    1460            4 :       CPASSERT(RI_size_optimized == expected_RI_size)
    1461           16 :       CPASSERT(ALL(bs_env%auto_ri%sizes_opt_RI > 0))
    1462            4 :       IF (RI_size_optimized > RI_size_ref) THEN
    1463            0 :          CPABORT("AUTO_RI request exceeds independent reference RI directions")
    1464              :       END IF
    1465            4 :       size_U_Pp_AB = 0
    1466           16 :       DO iatom = 1, bs_env%n_atom
    1467           16 :          size_U_Pp_AB = size_U_Pp_AB + sizes_ref_RI(iatom)*sizes_opt_RI_AA(iatom)
    1468              :       END DO
    1469           10 :       DO k = 1, AB_pair_count
    1470              :          size_U_Pp_AB = size_U_Pp_AB + &
    1471              :                         (sizes_ref_RI(AB_atom_A(k)) + &
    1472           10 :                          sizes_ref_RI(AB_atom_B(k)))*sizes_opt_RI_AB(k)
    1473              :       END DO
    1474           30 :       ALLOCATE (bs_env%auto_ri%AB_atom_A(bs_env%n_atom + AB_pair_count), source=0)
    1475           30 :       ALLOCATE (bs_env%auto_ri%AB_atom_B(bs_env%n_atom + AB_pair_count), source=0)
    1476           30 :       ALLOCATE (bs_env%auto_ri%AB_first_p_A(bs_env%n_atom + AB_pair_count), source=0)
    1477           30 :       ALLOCATE (bs_env%auto_ri%AB_first_p_B(bs_env%n_atom + AB_pair_count), source=0)
    1478           30 :       ALLOCATE (bs_env%auto_ri%AB_size_opt_RI_to_A(bs_env%n_atom + AB_pair_count), source=0)
    1479           30 :       ALLOCATE (bs_env%auto_ri%AB_size_ref_RI(bs_env%n_atom + AB_pair_count), source=0)
    1480           30 :       ALLOCATE (bs_env%auto_ri%AB_size_opt_RI(bs_env%n_atom + AB_pair_count), source=0)
    1481           30 :       ALLOCATE (bs_env%auto_ri%U_Pp_AB_offset(bs_env%n_atom + AB_pair_count), source=0)
    1482         7696 :       ALLOCATE (bs_env%auto_ri%U_Pp_AB(size_U_Pp_AB), source=0.0_dp)
    1483            4 :       bs_env%auto_ri%AB_block_count = 0
    1484              : 
    1485           12 :       ALLOCATE (block_next(bs_env%n_atom))
    1486            4 :       block_next(1) = 1
    1487           12 :       DO iatom = 2, bs_env%n_atom
    1488           12 :          block_next(iatom) = block_next(iatom - 1) + bs_env%auto_ri%sizes_opt_RI(iatom - 1)
    1489              :       END DO
    1490           16 :       DO iatom = 1, bs_env%n_atom
    1491           12 :          IF (sizes_opt_RI_AA(iatom) > 0) bs_env%auto_ri%AB_block_count = &
    1492           12 :             bs_env%auto_ri%AB_block_count + 1
    1493              :          CALL store_AA_columns(bs_env, iatom, &
    1494              :                                U_Pp_A(1:sizes_ref_RI(iatom), 1:sizes_opt_RI_AA(iatom), iatom), &
    1495           16 :                                block_next)
    1496              :       END DO
    1497              : 
    1498           10 :       DO k = 1, AB_pair_count
    1499            6 :          iatom = AB_atom_A(k)
    1500            6 :          jatom = AB_atom_B(k)
    1501            6 :          size_opt_RI_AB = sizes_opt_RI_AB(k)
    1502            6 :          IF (size_opt_RI_AB == 0) CYCLE
    1503            6 :          root_rank = MODULO(k - 1, para%num_pe)
    1504            6 :          nRI_AB_ref = sizes_ref_RI(iatom) + sizes_ref_RI(jatom)
    1505           24 :          ALLOCATE (U_Pp_AB(nRI_AB_ref, size_opt_RI_AB))
    1506            6 :          IF (para%mepos == root_rank) THEN
    1507              :             U_Pp_AB(:, :) = RESHAPE( &
    1508              :                             U_Pp_AB_packed(U_Pp_AB_offset(k): &
    1509              :                                            U_Pp_AB_offset(k) + &
    1510              :                                            nRI_AB_ref*size_opt_RI_AB - 1), &
    1511            9 :                             [nRI_AB_ref, size_opt_RI_AB])
    1512              :          END IF
    1513            6 :          CALL para%bcast(U_Pp_AB, root_rank)
    1514            6 :          bs_env%auto_ri%AB_block_count = bs_env%auto_ri%AB_block_count + 1
    1515              :          ! Eq. (13): store the selected neighbor-pair contractions in the global transform.
    1516            6 :          CALL store_AB_columns(bs_env, U_Pp_AB, iatom, jatom, AB_size_opt_RI_to_A(k), block_next)
    1517           10 :          DEALLOCATE (U_Pp_AB)
    1518              :       END DO
    1519              : 
    1520           16 :       DO iatom = 1, bs_env%n_atom
    1521           36 :          expected_RI_size = 1 + SUM(bs_env%auto_ri%sizes_opt_RI(:iatom))
    1522           16 :          CPASSERT(block_next(iatom) == expected_RI_size)
    1523              :       END DO
    1524            0 :       DEALLOCATE (block_next, AB_atom_A, AB_atom_B, AB_size_opt_RI_to_A, &
    1525            4 :                   U_Pp_AB_offset, U_Pp_AB_packed)
    1526              : 
    1527            4 :       CALL timestop(handle)
    1528              : 
    1529           16 :    END SUBROUTINE compute_AB_optimized_RI_basis
    1530              : 
    1531              : ! **************************************************************************************************
    1532              : !> \brief Divides AB columns between atoms A and B for load balance.
    1533              : !> \param load_a current number of columns owned by A
    1534              : !> \param load_b current number of columns owned by B
    1535              : !> \param RI_size_opt_AB number of new pair RI functions
    1536              : !> \return ...
    1537              : ! **************************************************************************************************
    1538            6 :    PURE INTEGER FUNCTION balance_AB_columns(load_a, load_b, RI_size_opt_AB) RESULT(RI_size_to_a)
    1539              :       INTEGER, INTENT(IN)                                :: load_a, load_b, RI_size_opt_AB
    1540              : 
    1541            6 :       RI_size_to_a = (load_b + RI_size_opt_AB - load_a + 1)/2
    1542            6 :       RI_size_to_a = MAX(0, MIN(RI_size_opt_AB, RI_size_to_a))
    1543              : 
    1544            6 :    END FUNCTION balance_AB_columns
    1545              : 
    1546              : ! **************************************************************************************************
    1547              : !> \brief Stores the AA contraction columns for one atom.
    1548              : !> \param bs_env ...
    1549              : !> \param atom ...
    1550              : !> \param U_Pp_A atom-local contraction coefficients
    1551              : !> \param block_next ...
    1552              : ! **************************************************************************************************
    1553           12 :    SUBROUTINE store_AA_columns(bs_env, atom, U_Pp_A, block_next)
    1554              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1555              :       INTEGER, INTENT(IN)                                :: atom
    1556              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: U_Pp_A
    1557              :       INTEGER, DIMENSION(:), INTENT(INOUT)               :: block_next
    1558              : 
    1559              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'store_AA_columns'
    1560              : 
    1561              :       INTEGER                                            :: AB_block, first, handle, RI_size_opt_AA
    1562              :       REAL(KIND=dp), ALLOCATABLE                         :: U_Pp_A_normalized(:, :)
    1563              : 
    1564           12 :       CALL timeset(routineN, handle)
    1565              : 
    1566           12 :       first = block_next(atom)
    1567           12 :       AB_block = bs_env%auto_ri%AB_block_count
    1568           12 :       RI_size_opt_AA = SIZE(U_Pp_A, 2)
    1569           48 :       ALLOCATE (U_Pp_A_normalized(SIZE(U_Pp_A, 1), RI_size_opt_AA))
    1570           12 :       IF (RI_size_opt_AA > 0) THEN
    1571         4588 :          U_Pp_A_normalized(:, :) = U_Pp_A
    1572           12 :          CALL normalize_RI_coefficients(U_Pp_A_normalized)
    1573           12 :          bs_env%auto_ri%AB_atom_A(AB_block) = atom
    1574           12 :          bs_env%auto_ri%AB_atom_B(AB_block) = atom
    1575              :          bs_env%auto_ri%AB_first_p_A(AB_block) = &
    1576           24 :             first - SUM(bs_env%auto_ri%sizes_opt_RI(:atom - 1))
    1577           12 :          bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block) = RI_size_opt_AA
    1578           12 :          CALL store_U_Pp_AB(bs_env%auto_ri, AB_block, U_Pp_A_normalized)
    1579              :       END IF
    1580           12 :       DEALLOCATE (U_Pp_A_normalized)
    1581           12 :       block_next(atom) = first + RI_size_opt_AA
    1582              : 
    1583           12 :       CALL timestop(handle)
    1584              : 
    1585           12 :    END SUBROUTINE store_AA_columns
    1586              : 
    1587              : ! **************************************************************************************************
    1588              : !> \brief Stores an AB contraction in the blocks assigned to atoms A and B.
    1589              : !> \param bs_env ...
    1590              : !> \param U_Pp_AB neighbor-pair contractions
    1591              : !> \param atom_A ...
    1592              : !> \param atom_B ...
    1593              : !> \param RI_size_to_a number of optimized pair RI functions assigned to atom A
    1594              : !> \param block_next ...
    1595              : ! **************************************************************************************************
    1596            6 :    SUBROUTINE store_AB_columns(bs_env, U_Pp_AB, atom_A, atom_B, RI_size_to_a, block_next)
    1597              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1598              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1599              :          INTENT(INOUT)                                   :: U_Pp_AB
    1600              :       INTEGER, INTENT(IN)                                :: atom_A, atom_B, RI_size_to_a
    1601              :       INTEGER, DIMENSION(:), INTENT(INOUT)               :: block_next
    1602              : 
    1603              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'store_AB_columns'
    1604              : 
    1605              :       INTEGER                                            :: AB_block, first_a, first_b, handle, &
    1606              :                                                             RI_size_opt_AB
    1607              : 
    1608            6 :       CALL timeset(routineN, handle)
    1609              : 
    1610            6 :       RI_size_opt_AB = SIZE(U_Pp_AB, 2)
    1611            6 :       AB_block = bs_env%auto_ri%AB_block_count
    1612            6 :       first_a = block_next(atom_A)
    1613            6 :       first_b = block_next(atom_B)
    1614            6 :       CALL normalize_RI_coefficients(U_Pp_AB)
    1615              : 
    1616            6 :       bs_env%auto_ri%AB_atom_A(AB_block) = atom_A
    1617            6 :       bs_env%auto_ri%AB_atom_B(AB_block) = atom_B
    1618            8 :       bs_env%auto_ri%AB_first_p_A(AB_block) = first_a - SUM(bs_env%auto_ri%sizes_opt_RI(:atom_A - 1))
    1619           16 :       bs_env%auto_ri%AB_first_p_B(AB_block) = first_b - SUM(bs_env%auto_ri%sizes_opt_RI(:atom_B - 1))
    1620            6 :       bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block) = RI_size_to_a
    1621            6 :       CALL store_U_Pp_AB(bs_env%auto_ri, AB_block, U_Pp_AB)
    1622            6 :       block_next(atom_A) = first_a + RI_size_to_a
    1623            6 :       block_next(atom_B) = first_b + RI_size_opt_AB - RI_size_to_a
    1624              : 
    1625            6 :       CALL timestop(handle)
    1626              : 
    1627            6 :    END SUBROUTINE store_AB_columns
    1628              : 
    1629              : ! **************************************************************************************************
    1630              : !> \brief Packs one AB U_Pp matrix into the persistent AUTO_RI data; B=A denotes an AA block.
    1631              : !> \param auto_ri ...
    1632              : !> \param AB_block ...
    1633              : !> \param U_Pp ...
    1634              : ! **************************************************************************************************
    1635           18 :    SUBROUTINE store_U_Pp_AB(auto_ri, AB_block, U_Pp)
    1636              :       TYPE(auto_ri_type), INTENT(INOUT)                  :: auto_ri
    1637              :       INTEGER, INTENT(IN)                                :: AB_block
    1638              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: U_Pp
    1639              : 
    1640              :       INTEGER                                            :: first, last
    1641              : 
    1642           18 :       first = 1
    1643           18 :       IF (AB_block > 1) THEN
    1644              :          first = auto_ri%U_Pp_AB_offset(AB_block - 1) + &
    1645              :                  auto_ri%AB_size_ref_RI(AB_block - 1)* &
    1646           14 :                  auto_ri%AB_size_opt_RI(AB_block - 1)
    1647              :       END IF
    1648           54 :       last = first + SIZE(U_Pp) - 1
    1649           18 :       CPASSERT(last <= SIZE(auto_ri%U_Pp_AB))
    1650           18 :       auto_ri%U_Pp_AB_offset(AB_block) = first
    1651           18 :       auto_ri%AB_size_ref_RI(AB_block) = SIZE(U_Pp, 1)
    1652           18 :       auto_ri%AB_size_opt_RI(AB_block) = SIZE(U_Pp, 2)
    1653         7756 :       auto_ri%U_Pp_AB(first:last) = RESHAPE(U_Pp, [SIZE(U_Pp)])
    1654              : 
    1655           18 :    END SUBROUTINE store_U_Pp_AB
    1656              : 
    1657              : ! **************************************************************************************************
    1658              : !> \brief Computes the two-center matrix B_PQ^AB in Eqs. (9)-(12).
    1659              : !>
    1660              : !>        Its numerator uses the symmetric off-diagonal density
    1661              : !>        ρ̅_mn^AB=ρ_mn^AB+ρ_mn^BA after projection from the retained AA
    1662              : !>        space. Its normalization uses the complete two-atom density.
    1663              : !> \param bs_env ...
    1664              : !> \param ctx ...
    1665              : !> \param ws ...
    1666              : !> \param iatom ...
    1667              : !> \param jatom ...
    1668              : !> \param C_mu_n_AB molecular-orbital coefficients on the AO rows of atoms A and B
    1669              : !> \param M_PQ combined metric of the large reference RI bases on A and B
    1670              : !> \param B_PQ normalized two-center matrix B_PQ^AB
    1671              : ! **************************************************************************************************
    1672            3 :    SUBROUTINE compute_B_PQ_AB(bs_env, ctx, ws, iatom, jatom, C_mu_n_AB, M_PQ, B_PQ)
    1673              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1674              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1675              :       TYPE(gw_3c_ws_type), INTENT(INOUT)                 :: ws
    1676              :       INTEGER, INTENT(IN)                                :: iatom, jatom
    1677              :       REAL(KIND=dp), INTENT(IN)                          :: C_mu_n_AB(:, :, :)
    1678              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: M_PQ(:, :), B_PQ(:, :)
    1679              : 
    1680              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_B_PQ_AB'
    1681              : 
    1682              :       INTEGER :: atoms(2), handle, homo, i, j, k, kind_a, kind_b, nAO_AB, nRI_AB_ref, &
    1683              :          offset_AO(2), offset_ref_RI(2), rank, sizes_AO(2), sizes_ref_RI(2), spin
    1684              :       LOGICAL                                            :: screened
    1685              :       REAL(KIND=dp)                                      :: norm_exchange, norm_transition
    1686            3 :       REAL(KIND=dp), ALLOCATABLE :: B_Pi(:, :), B_PQ_exchange_AB(:, :), B_PQ_exchange_full(:, :), &
    1687            3 :          B_PQ_spin(:, :), B_PQ_transition_AB(:, :), B_PQ_transition_full(:, :), &
    1688            3 :          Int_3c_AB(:, :, :), Int_3c_all(:, :, :), M_PQ_block(:, :), weights(:), X_Pi(:, :)
    1689              : 
    1690            3 :       CALL timeset(routineN, handle)
    1691              : 
    1692            3 :       atoms(1) = iatom
    1693            3 :       atoms(2) = jatom
    1694            9 :       sizes_AO = bs_env%sizes_AO(atoms)
    1695            9 :       DO i = 1, 2
    1696            9 :          sizes_ref_RI(i) = get_ref_RI_size(bs_env, atoms(i))
    1697              :       END DO
    1698            3 :       offset_AO(1) = 0
    1699            3 :       offset_AO(2) = sizes_AO(1)
    1700            3 :       offset_ref_RI(1) = 0
    1701            3 :       offset_ref_RI(2) = sizes_ref_RI(1)
    1702            9 :       nAO_AB = SUM(sizes_AO)
    1703            9 :       nRI_AB_ref = SUM(sizes_ref_RI)
    1704            0 :       ALLOCATE (B_PQ(nRI_AB_ref, nRI_AB_ref), &
    1705           18 :                 M_PQ(nRI_AB_ref, nRI_AB_ref), source=0.0_dp)
    1706           15 :       ALLOCATE (Int_3c_all(nAO_AB, nAO_AB, nRI_AB_ref), source=0.0_dp)
    1707            9 :       DO i = 1, 2
    1708           21 :          DO j = 1, 2
    1709              :             CALL compute_M_PQ_AB( &
    1710           12 :                bs_env, atoms(i), atoms(j), M_PQ_block, bs_env%ri_metric)
    1711              :             M_PQ(offset_ref_RI(i) + 1:offset_ref_RI(i) + sizes_ref_RI(i), &
    1712        80042 :                  offset_ref_RI(j) + 1:offset_ref_RI(j) + sizes_ref_RI(j)) = M_PQ_block
    1713           12 :             DEALLOCATE (M_PQ_block)
    1714           42 :             DO k = 1, 2
    1715              :                CALL build_3c_integral_block_ctx( &
    1716              :                   Int_3c_all, ctx, ws, atom_j=atoms(j), atom_k=atoms(k), &
    1717              :                   atom_i=atoms(i), j_offset=offset_AO(j), k_offset=offset_AO(k), &
    1718           36 :                   i_offset=offset_ref_RI(i), screened=screened)
    1719              :             END DO
    1720              :          END DO
    1721              :       END DO
    1722            3 :       kind_a = bs_env%ri_rs%particle_set(iatom)%atomic_kind%kind_number
    1723            3 :       kind_b = bs_env%ri_rs%particle_set(jatom)%atomic_kind%kind_number
    1724              :       CALL add_RI_metric_regularization(bs_env%basis_set_RI(kind_a)%gto_basis_set, &
    1725            3 :                                         bs_env%regularization_RI, M_PQ)
    1726              :       CALL add_RI_metric_regularization(bs_env%basis_set_RI(kind_b)%gto_basis_set, &
    1727              :                                         bs_env%regularization_RI, M_PQ, &
    1728            3 :                                         offset=sizes_ref_RI(1))
    1729        16523 :       ALLOCATE (Int_3c_AB, source=Int_3c_all)
    1730        11679 :       Int_3c_AB(1:sizes_AO(1), 1:sizes_AO(1), :) = 0.0_dp
    1731         1431 :       Int_3c_AB(sizes_AO(1) + 1:, sizes_AO(1) + 1:, :) = 0.0_dp
    1732           15 :       ALLOCATE (weights(bs_env%n_mo_retained), B_PQ_spin(nRI_AB_ref, nRI_AB_ref))
    1733              :       ALLOCATE (B_PQ_transition_AB(nRI_AB_ref, nRI_AB_ref), &
    1734              :                 B_PQ_exchange_AB(nRI_AB_ref, nRI_AB_ref), &
    1735              :                 B_PQ_transition_full(nRI_AB_ref, nRI_AB_ref), &
    1736              :                 B_PQ_exchange_full(nRI_AB_ref, nRI_AB_ref), &
    1737           27 :                 source=0.0_dp)
    1738            6 :       DO spin = 1, bs_env%n_spin
    1739            3 :          homo = bs_env%n_occ(spin)
    1740              :          CALL compute_orbital_weights( &
    1741              :             eigenval_MO=bs_env%eigenval_scf_Gamma(1:bs_env%n_mo_retained, spin), &
    1742            3 :             homo=homo, weights=weights)
    1743          122 :          CALL compute_B_PQ_transition(Int_3c_all, C_mu_n_AB(:, :, spin), weights, homo, B_PQ_spin)
    1744        79557 :          B_PQ_transition_full(:, :) = B_PQ_transition_full + B_PQ_spin
    1745          122 :          CALL compute_B_PQ_transition(Int_3c_AB, C_mu_n_AB(:, :, spin), weights, homo, B_PQ_spin)
    1746        79557 :          B_PQ_transition_AB(:, :) = B_PQ_transition_AB + B_PQ_spin
    1747              :          CALL compute_B_PQ_exchange(Int_3c_all, C_mu_n_AB(:, :, spin), &
    1748              :                                     bs_env%eigenval_scf_Gamma(1:bs_env%n_mo_retained, spin), &
    1749          122 :                                     homo, bs_env%auto_ri%occ_energy_window, B_PQ_spin)
    1750        79557 :          B_PQ_exchange_full(:, :) = B_PQ_exchange_full + B_PQ_spin
    1751              :          CALL compute_B_PQ_exchange(Int_3c_AB, C_mu_n_AB(:, :, spin), &
    1752              :                                     bs_env%eigenval_scf_Gamma(1:bs_env%n_mo_retained, spin), &
    1753          122 :                                     homo, bs_env%auto_ri%occ_energy_window, B_PQ_spin)
    1754        79560 :          B_PQ_exchange_AB(:, :) = B_PQ_exchange_AB + B_PQ_spin
    1755              :       END DO
    1756            3 :       CALL compute_reference_M_PQ_inv_factor(M_PQ, X_Pi, rank)
    1757            3 :       DEALLOCATE (B_PQ_spin)
    1758           12 :       ALLOCATE (B_Pi(nRI_AB_ref, rank))
    1759            3 :       B_Pi(:, :) = MATMUL(B_PQ_transition_full, X_Pi)
    1760        79557 :       norm_transition = SUM(X_Pi*B_Pi)
    1761            3 :       B_Pi(:, :) = MATMUL(B_PQ_exchange_full, X_Pi)
    1762        79557 :       norm_exchange = SUM(X_Pi*B_Pi)
    1763              :       ! Eqs. (10)-(12): B_PQ^AB = (B_PQ^{AB,C}/N_C^{AB}+B_PQ^{AB,X}/N_X^{AB})/2.
    1764            3 :       IF (norm_transition > 0.0_dp) THEN
    1765        79557 :          B_PQ(:, :) = B_PQ + 0.5_dp*B_PQ_transition_AB/norm_transition
    1766              :       END IF
    1767            3 :       IF (norm_exchange > 0.0_dp) THEN
    1768        79557 :          B_PQ(:, :) = B_PQ + 0.5_dp*B_PQ_exchange_AB/norm_exchange
    1769              :       END IF
    1770              : 
    1771            3 :       CALL timestop(handle)
    1772              : 
    1773            6 :    END SUBROUTINE compute_B_PQ_AB
    1774              : 
    1775              : ! **************************************************************************************************
    1776              : !> \brief Builds lists of AB blocks whose columns belong to each atom; B=A denotes an AA block.
    1777              : !> \param bs_env ...
    1778              : !> \param lists ...
    1779              : ! **************************************************************************************************
    1780            4 :    SUBROUTINE build_AB_block_lists(bs_env, lists)
    1781              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1782              :       TYPE(AB_block_list_type), ALLOCATABLE, INTENT(OUT) :: lists(:)
    1783              : 
    1784              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_AB_block_lists'
    1785              : 
    1786              :       INTEGER                                            :: AB_block, atom_A, atom_B, handle, &
    1787              :                                                             position, RI_size_to_a, size_opt_RI_AB
    1788            4 :       INTEGER, ALLOCATABLE                               :: n_AB_blocks_by_atom(:)
    1789              : 
    1790            4 :       CALL timeset(routineN, handle)
    1791              : 
    1792           24 :       ALLOCATE (lists(bs_env%n_atom))
    1793           12 :       ALLOCATE (n_AB_blocks_by_atom(bs_env%n_atom), source=0)
    1794           22 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1795           18 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1796           18 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1797           18 :          size_opt_RI_AB = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1798           18 :          RI_size_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1799           18 :          IF (RI_size_to_a > 0) n_AB_blocks_by_atom(atom_A) = n_AB_blocks_by_atom(atom_A) + 1
    1800           22 :          IF (atom_B /= atom_A .AND. RI_size_to_a < size_opt_RI_AB) THEN
    1801            4 :             n_AB_blocks_by_atom(atom_B) = n_AB_blocks_by_atom(atom_B) + 1
    1802              :          END IF
    1803              :       END DO
    1804              : 
    1805           16 :       DO atom_A = 1, bs_env%n_atom
    1806           36 :          ALLOCATE (lists(atom_A)%block(n_AB_blocks_by_atom(atom_A)))
    1807           24 :          ALLOCATE (lists(atom_A)%first_p_AB(n_AB_blocks_by_atom(atom_A)))
    1808           24 :          ALLOCATE (lists(atom_A)%first_p_atom(n_AB_blocks_by_atom(atom_A)))
    1809           28 :          ALLOCATE (lists(atom_A)%size_opt_RI(n_AB_blocks_by_atom(atom_A)))
    1810              :       END DO
    1811            4 :       n_AB_blocks_by_atom = 0
    1812           22 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1813           18 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1814           18 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1815           18 :          size_opt_RI_AB = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1816           18 :          RI_size_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1817           18 :          IF (RI_size_to_a > 0) THEN
    1818           14 :             position = n_AB_blocks_by_atom(atom_A) + 1
    1819           14 :             n_AB_blocks_by_atom(atom_A) = position
    1820           14 :             lists(atom_A)%block(position) = AB_block
    1821           14 :             lists(atom_A)%first_p_AB(position) = 1
    1822           14 :             lists(atom_A)%first_p_atom(position) = bs_env%auto_ri%AB_first_p_A(AB_block)
    1823           14 :             lists(atom_A)%size_opt_RI(position) = RI_size_to_a
    1824              :          END IF
    1825           22 :          IF (atom_B /= atom_A .AND. RI_size_to_a < size_opt_RI_AB) THEN
    1826            4 :             position = n_AB_blocks_by_atom(atom_B) + 1
    1827            4 :             n_AB_blocks_by_atom(atom_B) = position
    1828            4 :             lists(atom_B)%block(position) = AB_block
    1829            4 :             lists(atom_B)%first_p_AB(position) = RI_size_to_a + 1
    1830              :             lists(atom_B)%first_p_atom(position) = &
    1831            4 :                bs_env%auto_ri%AB_first_p_B(AB_block)
    1832            4 :             lists(atom_B)%size_opt_RI(position) = size_opt_RI_AB - RI_size_to_a
    1833              :          END IF
    1834              :       END DO
    1835           16 :       DO atom_A = 1, bs_env%n_atom
    1836           34 :          IF (SUM(lists(atom_A)%size_opt_RI) /= bs_env%auto_ri%sizes_opt_RI(atom_A)) THEN
    1837            0 :             CPABORT("AUTO_RI AB-block columns do not fill the optimized atom block")
    1838              :          END IF
    1839              :       END DO
    1840            4 :       DEALLOCATE (n_AB_blocks_by_atom)
    1841              : 
    1842            4 :       CALL timestop(handle)
    1843              : 
    1844            4 :    END SUBROUTINE build_AB_block_lists
    1845              : 
    1846              : ! **************************************************************************************************
    1847              : !> \brief Returns the location of atom in atoms(:count), or zero if it is absent.
    1848              : !> \param atom ...
    1849              : !> \param atoms ...
    1850              : !> \param count ...
    1851              : !> \return ...
    1852              : ! **************************************************************************************************
    1853          128 :    PURE INTEGER FUNCTION find_atom_position(atom, atoms, count) RESULT(position)
    1854              :       INTEGER, INTENT(IN)                                :: atom
    1855              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: atoms
    1856              :       INTEGER, INTENT(IN)                                :: count
    1857              : 
    1858              :       INTEGER                                            :: i
    1859              : 
    1860          128 :       position = 0
    1861          172 :       DO i = 1, count
    1862          172 :          IF (atoms(i) == atom) THEN
    1863          128 :             position = i
    1864              :             RETURN
    1865              :          END IF
    1866              :       END DO
    1867              : 
    1868              :    END FUNCTION find_atom_position
    1869              : 
    1870              : ! **************************************************************************************************
    1871              : !> \brief Collects U_Pp from the large reference basis for one optimized atom block.
    1872              : !> \param bs_env ...
    1873              : !> \param atom ...
    1874              : !> \param list ...
    1875              : !> \param atom_U ...
    1876              : ! **************************************************************************************************
    1877           32 :    SUBROUTINE build_optimized_atom_U(bs_env, atom, list, atom_U)
    1878              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1879              :       INTEGER, INTENT(IN)                                :: atom
    1880              :       TYPE(AB_block_list_type), INTENT(IN)               :: list
    1881              :       TYPE(optimized_atom_U_type), INTENT(OUT)           :: atom_U
    1882              : 
    1883              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_optimized_atom_U'
    1884              : 
    1885              :       INTEGER :: AB_block, atom_A, atom_B, first, first_p_AB, first_p_atom, first_row, handle, i, &
    1886              :          last, nRI_ref_A, nRI_ref_block, nsupport, position, size_opt_RI_AB
    1887              :       INTEGER, ALLOCATABLE                               :: support_atoms(:)
    1888           32 :       REAL(KIND=dp), ALLOCATABLE                         :: U_Pp_AB(:, :)
    1889              : 
    1890           32 :       CALL timeset(routineN, handle)
    1891              : 
    1892           96 :       ALLOCATE (support_atoms(2*SIZE(list%block)))
    1893           32 :       nsupport = 0
    1894           80 :       DO i = 1, SIZE(list%block)
    1895           48 :          AB_block = list%block(i)
    1896           48 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1897           48 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1898           48 :          position = find_atom_position(atom_A, support_atoms, nsupport)
    1899           48 :          IF (position == 0) THEN
    1900           42 :             nsupport = nsupport + 1
    1901           42 :             support_atoms(nsupport) = atom_A
    1902              :          END IF
    1903           80 :          IF (atom_B /= atom_A) THEN
    1904           16 :             position = find_atom_position(atom_B, support_atoms, nsupport)
    1905           16 :             IF (position == 0) THEN
    1906            6 :                nsupport = nsupport + 1
    1907            6 :                support_atoms(nsupport) = atom_B
    1908              :             END IF
    1909              :          END IF
    1910              :       END DO
    1911           32 :       CPASSERT(nsupport > 0)
    1912          128 :       ALLOCATE (atom_U%ref_atom(nsupport), atom_U%first_P(nsupport))
    1913           80 :       atom_U%ref_atom(:) = support_atoms(:nsupport)
    1914              :       first_row = 1
    1915           80 :       DO i = 1, nsupport
    1916           48 :          atom_U%first_P(i) = first_row
    1917           80 :          first_row = first_row + get_ref_RI_size(bs_env, atom_U%ref_atom(i))
    1918              :       END DO
    1919            0 :       ALLOCATE (atom_U%U_Pp(first_row - 1, bs_env%auto_ri%sizes_opt_RI(atom)), &
    1920        25356 :                 source=0.0_dp)
    1921              : 
    1922           80 :       DO i = 1, SIZE(list%block)
    1923           48 :          AB_block = list%block(i)
    1924           48 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1925           48 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1926           48 :          nRI_ref_block = bs_env%auto_ri%AB_size_ref_RI(AB_block)
    1927           48 :          size_opt_RI_AB = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1928           48 :          first = bs_env%auto_ri%U_Pp_AB_offset(AB_block)
    1929           48 :          last = first + nRI_ref_block*size_opt_RI_AB - 1
    1930          192 :          ALLOCATE (U_Pp_AB(nRI_ref_block, size_opt_RI_AB))
    1931              :          U_Pp_AB(:, :) = RESHAPE(bs_env%auto_ri%U_Pp_AB(first:last), &
    1932          144 :                                  [nRI_ref_block, size_opt_RI_AB])
    1933           48 :          first_p_AB = list%first_p_AB(i)
    1934           48 :          first_p_atom = list%first_p_atom(i)
    1935           48 :          size_opt_RI_AB = list%size_opt_RI(i)
    1936           48 :          IF (first_p_AB < 1 .OR. &
    1937              :              first_p_AB + size_opt_RI_AB - 1 > SIZE(U_Pp_AB, 2)) THEN
    1938            0 :             CPABORT("AUTO_RI AB-block column range is invalid")
    1939              :          END IF
    1940           48 :          IF (first_p_atom < 1 .OR. &
    1941              :              first_p_atom + size_opt_RI_AB - 1 > bs_env%auto_ri%sizes_opt_RI(atom)) THEN
    1942            0 :             CPABORT("AUTO_RI optimized atom-block column range is invalid")
    1943              :          END IF
    1944              : 
    1945           48 :          nRI_ref_A = get_ref_RI_size(bs_env, atom_A)
    1946           48 :          position = find_atom_position(atom_A, atom_U%ref_atom, nsupport)
    1947           48 :          CPASSERT(position > 0)
    1948           48 :          first_row = atom_U%first_P(position)
    1949              :          atom_U%U_Pp(first_row:first_row + nRI_ref_A - 1, &
    1950              :                      first_p_atom:first_p_atom + size_opt_RI_AB - 1) = &
    1951        19048 :             U_Pp_AB(1:nRI_ref_A, first_p_AB:first_p_AB + size_opt_RI_AB - 1)
    1952           48 :          IF (atom_B /= atom_A) THEN
    1953           16 :             position = find_atom_position(atom_B, atom_U%ref_atom, nsupport)
    1954           16 :             CPASSERT(position > 0)
    1955           16 :             first_row = atom_U%first_P(position)
    1956              :             atom_U%U_Pp(first_row:first_row + get_ref_RI_size(bs_env, atom_B) - 1, &
    1957              :                         first_p_atom:first_p_atom + size_opt_RI_AB - 1) = &
    1958         2592 :                U_Pp_AB(nRI_ref_A + 1:, first_p_AB:first_p_AB + size_opt_RI_AB - 1)
    1959              :          END IF
    1960           80 :          DEALLOCATE (U_Pp_AB)
    1961              :       END DO
    1962           32 :       DEALLOCATE (support_atoms)
    1963              : 
    1964           32 :       CALL timestop(handle)
    1965              : 
    1966           64 :    END SUBROUTINE build_optimized_atom_U
    1967              : 
    1968              : ! **************************************************************************************************
    1969              : !> \brief Adds REGULARIZATION_RI to one atom-local reference metric block.
    1970              : !> \param bs_env ...
    1971              : !> \param atom ...
    1972              : !> \param M_PQ atom-local metric block in the large reference RI basis
    1973              : ! **************************************************************************************************
    1974            0 :    SUBROUTINE regularize_M_PQ_block(bs_env, atom, M_PQ)
    1975              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1976              :       INTEGER, INTENT(IN)                                :: atom
    1977              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: M_PQ
    1978              : 
    1979              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'regularize_M_PQ_block'
    1980              : 
    1981              :       INTEGER                                            :: handle, i, iset, kind, nset
    1982            0 :       INTEGER, DIMENSION(:), POINTER                     :: npgf, nsgf_set
    1983            0 :       INTEGER, DIMENSION(:, :), POINTER                  :: first_sgf
    1984            0 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: zet
    1985              : 
    1986            0 :       CALL timeset(routineN, handle)
    1987              : 
    1988            0 :       CPASSERT(SIZE(M_PQ, 1) == SIZE(M_PQ, 2))
    1989            0 :       kind = bs_env%ri_rs%particle_set(atom)%atomic_kind%kind_number
    1990              :       CALL get_gto_basis_set(bs_env%basis_set_RI(kind)%gto_basis_set, &
    1991              :                              first_sgf=first_sgf, npgf=npgf, nset=nset, &
    1992            0 :                              nsgf_set=nsgf_set, zet=zet)
    1993            0 :       DO iset = 1, nset
    1994            0 :          DO i = first_sgf(1, iset), first_sgf(1, iset) + nsgf_set(iset) - 1
    1995              :             M_PQ(i, i) = M_PQ(i, i) + &
    1996              :                          bs_env%regularization_RI* &
    1997            0 :                          MAX(1.0_dp, 1.0_dp/MINVAL(zet(1:npgf(iset), iset)))
    1998              :          END DO
    1999              :       END DO
    2000              : 
    2001            0 :       CALL timestop(handle)
    2002              : 
    2003            0 :    END SUBROUTINE regularize_M_PQ_block
    2004              : 
    2005              : ! **************************************************************************************************
    2006              : !> \brief Directly contracts one optimized atom-pair metric block.
    2007              : !>
    2008              : !>        M_pq^{AB} = Σ_IJ (U_Ip^A)^T M_IJ U_Jq^B. Each reference
    2009              : !>        atom-pair block M_IJ is discarded immediately after this contraction.
    2010              : !> \param bs_env ...
    2011              : !> \param atom_U_a contractions U_Ip for optimized block A
    2012              : !> \param atom_U_b contractions U_Jq for optimized block B
    2013              : !> \param coulomb true for Coulomb; false for the GW fitting operator
    2014              : !> \param symmetric true when both transforms describe the same optimized block
    2015              : !> \param matrix_optimized contracted M_pq or V_pq block in the optimized RI basis
    2016              : ! **************************************************************************************************
    2017           24 :    SUBROUTINE compute_optimized_RI_matrix_block( &
    2018              :       bs_env, atom_U_a, atom_U_b, coulomb, symmetric, matrix_optimized)
    2019              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2020              :       TYPE(optimized_atom_U_type), INTENT(IN)            :: atom_U_a, atom_U_b
    2021              :       LOGICAL, INTENT(IN)                                :: coulomb, symmetric
    2022              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2023              :          INTENT(OUT)                                     :: matrix_optimized
    2024              : 
    2025              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_optimized_RI_matrix_block'
    2026              : 
    2027              :       INTEGER                                            :: atom_A, atom_B, first_a, first_b, &
    2028              :                                                             handle, i, j, last_a, last_b, &
    2029              :                                                             nRI_small_A, nRI_small_B
    2030           24 :       REAL(KIND=dp), ALLOCATABLE :: matrix_pq_contribution(:, :), matrix_reference(:, :), &
    2031           24 :          matrix_reference_times_U_b(:, :)
    2032              : 
    2033           24 :       CALL timeset(routineN, handle)
    2034              : 
    2035           24 :       nRI_small_A = SIZE(atom_U_a%U_Pp, 2)
    2036           24 :       nRI_small_B = SIZE(atom_U_b%U_Pp, 2)
    2037           24 :       IF (symmetric) THEN
    2038           12 :          CPASSERT(nRI_small_A == nRI_small_B)
    2039              :       END IF
    2040           96 :       ALLOCATE (matrix_optimized(nRI_small_A, nRI_small_B), source=0.0_dp)
    2041           62 :       DO i = 1, SIZE(atom_U_a%ref_atom)
    2042           38 :          atom_A = atom_U_a%ref_atom(i)
    2043           38 :          first_a = atom_U_a%first_P(i)
    2044           38 :          last_a = first_a + get_ref_RI_size(bs_env, atom_A) - 1
    2045          124 :          DO j = 1, SIZE(atom_U_b%ref_atom)
    2046           62 :             IF (symmetric .AND. j > i) CYCLE
    2047           54 :             atom_B = atom_U_b%ref_atom(j)
    2048           54 :             first_b = atom_U_b%first_P(j)
    2049           54 :             last_b = first_b + get_ref_RI_size(bs_env, atom_B) - 1
    2050           54 :             IF (coulomb) THEN
    2051           27 :                CALL compute_M_PQ_AB(bs_env, atom_A, atom_B, matrix_reference)
    2052              :             ELSE
    2053              :                CALL compute_M_PQ_AB( &
    2054           27 :                   bs_env, atom_A, atom_B, matrix_reference, bs_env%ri_metric)
    2055           27 :                IF (atom_A == atom_B .AND. bs_env%regularization_RI > 0.0_dp) THEN
    2056            0 :                   CALL regularize_M_PQ_block(bs_env, atom_A, matrix_reference)
    2057              :                END IF
    2058              :             END IF
    2059          216 :             ALLOCATE (matrix_reference_times_U_b(SIZE(matrix_reference, 1), nRI_small_B))
    2060              :             matrix_reference_times_U_b(:, :) = &
    2061       694010 :                MATMUL(matrix_reference, atom_U_b%U_Pp(first_b:last_b, :))
    2062          162 :             ALLOCATE (matrix_pq_contribution(nRI_small_A, nRI_small_B))
    2063              :             matrix_pq_contribution(:, :) = &
    2064           54 :                MATMUL(TRANSPOSE(atom_U_a%U_Pp(first_a:last_a, :)), &
    2065       153104 :                       matrix_reference_times_U_b)
    2066         2008 :             matrix_optimized(:, :) = matrix_optimized + matrix_pq_contribution
    2067           54 :             IF (symmetric .AND. i /= j) THEN
    2068          320 :                matrix_optimized(:, :) = matrix_optimized + TRANSPOSE(matrix_pq_contribution)
    2069              :             END IF
    2070          100 :             DEALLOCATE (matrix_pq_contribution, matrix_reference, matrix_reference_times_U_b)
    2071              :          END DO
    2072              :       END DO
    2073          798 :       IF (.NOT. ALL(ieee_is_finite(matrix_optimized))) THEN
    2074            0 :          CPABORT("AUTO_RI optimized metric block contains invalid values")
    2075              :       END IF
    2076              : 
    2077           24 :       CALL timestop(handle)
    2078              : 
    2079           48 :    END SUBROUTINE compute_optimized_RI_matrix_block
    2080              : 
    2081              : ! **************************************************************************************************
    2082              : !> \brief Builds M_opt or V_opt directly from contracted atom-pair blocks.
    2083              : !> \param qs_env ...
    2084              : !> \param bs_env ...
    2085              : !> \param lists ...
    2086              : !> \param matrix_pq contracted M_pq or V_pq in the optimized RI basis
    2087              : !> \param coulomb true for Coulomb; false for the GW fitting operator
    2088              : ! **************************************************************************************************
    2089            8 :    SUBROUTINE compute_contracted_RI_matrix(qs_env, bs_env, lists, matrix_pq, coulomb)
    2090              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2091              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2092              :       TYPE(AB_block_list_type), DIMENSION(:), INTENT(IN) :: lists
    2093              :       TYPE(cp_fm_type), INTENT(OUT)                      :: matrix_pq
    2094              :       LOGICAL, INTENT(IN)                                :: coulomb
    2095              : 
    2096              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_contracted_RI_matrix'
    2097              : 
    2098              :       INTEGER                                            :: handle, iatom, jatom, npcol, nprow, &
    2099              :                                                             pair_index, RI_size_opt
    2100            8 :       INTEGER, POINTER                                   :: col_dist(:), row_dist(:), sizes_opt_RI(:)
    2101              :       LOGICAL                                            :: atom_U_a_ready
    2102            8 :       REAL(KIND=dp), ALLOCATABLE                         :: matrix_pq_block(:, :)
    2103              :       TYPE(dbcsr_distribution_type)                      :: dist
    2104              :       TYPE(dbcsr_distribution_type), POINTER             :: template
    2105              :       TYPE(dbcsr_type)                                   :: blocks
    2106            8 :       TYPE(optimized_atom_U_type)                        :: atom_U_a, atom_U_b
    2107              : 
    2108            8 :       CALL timeset(routineN, handle)
    2109              : 
    2110           40 :       ALLOCATE (sizes_opt_RI(bs_env%n_atom), row_dist(bs_env%n_atom), col_dist(bs_env%n_atom))
    2111           64 :       sizes_opt_RI(:) = bs_env%auto_ri%sizes_opt_RI
    2112            8 :       CALL get_qs_env(qs_env, dbcsr_dist=template)
    2113            8 :       CALL dbcsr_distribution_get(template, nprows=nprow, npcols=npcol)
    2114           32 :       DO iatom = 1, SIZE(sizes_opt_RI)
    2115           24 :          row_dist(iatom) = MOD(iatom - 1, nprow)
    2116           32 :          col_dist(iatom) = MOD(iatom - 1, npcol)
    2117              :       END DO
    2118           32 :       RI_size_opt = SUM(sizes_opt_RI)
    2119            8 :       CALL create_distributed_matrix(qs_env, matrix_pq, RI_size_opt, RI_size_opt)
    2120            8 :       CALL dbcsr_distribution_new(dist, template=template, row_dist=row_dist, col_dist=col_dist)
    2121              :       CALL dbcsr_create(blocks, name='AUTO_RI contracted metric', dist=dist, &
    2122              :                         matrix_type=dbcsr_type_symmetric, &
    2123            8 :                         row_blk_size=sizes_opt_RI, col_blk_size=sizes_opt_RI)
    2124              : 
    2125            8 :       pair_index = 0
    2126           32 :       DO iatom = 1, SIZE(sizes_opt_RI)
    2127           24 :          atom_U_a_ready = .FALSE.
    2128           80 :          DO jatom = 1, iatom
    2129           48 :             pair_index = pair_index + 1
    2130           48 :             IF (MOD(pair_index - 1, bs_env%para_env%num_pe) /= bs_env%para_env%mepos) CYCLE
    2131           24 :             IF (.NOT. atom_U_a_ready) THEN
    2132           20 :                CALL build_optimized_atom_U(bs_env, iatom, lists(iatom), atom_U_a)
    2133           20 :                atom_U_a_ready = .TRUE.
    2134              :             END IF
    2135           24 :             IF (iatom == jatom) THEN
    2136              :                CALL compute_optimized_RI_matrix_block( &
    2137              :                   bs_env, atom_U_a, atom_U_a, coulomb, .TRUE., &
    2138           12 :                   matrix_pq_block)
    2139          912 :                matrix_pq_block(:, :) = 0.5_dp*(matrix_pq_block + TRANSPOSE(matrix_pq_block))
    2140              :             ELSE
    2141           12 :                CALL build_optimized_atom_U(bs_env, jatom, lists(jatom), atom_U_b)
    2142              :                CALL compute_optimized_RI_matrix_block( &
    2143              :                   bs_env, atom_U_a, atom_U_b, coulomb, .FALSE., &
    2144           12 :                   matrix_pq_block)
    2145              :             END IF
    2146           24 :             CALL dbcsr_put_block(blocks, iatom, jatom, matrix_pq_block)
    2147           72 :             DEALLOCATE (matrix_pq_block)
    2148              :          END DO
    2149              :       END DO
    2150            8 :       CALL dbcsr_finalize(blocks)
    2151            8 :       CALL copy_dbcsr_to_fm(blocks, matrix_pq)
    2152            8 :       CALL dbcsr_release(blocks)
    2153            8 :       CALL dbcsr_distribution_release(dist)
    2154            8 :       DEALLOCATE (sizes_opt_RI, row_dist, col_dist)
    2155              : 
    2156            8 :       CALL timestop(handle)
    2157              : 
    2158           32 :    END SUBROUTINE compute_contracted_RI_matrix
    2159              : 
    2160              : ! **************************************************************************************************
    2161              : !> \brief Computes the pseudoinverse of the optimized fitting metric M_opt.
    2162              : !>
    2163              : !>        The generalized problem M_opt X=M_AA X λ separates linearly
    2164              : !>        dependent directions. The result is M_opt^+=X diag(1/λ) X^T.
    2165              : !> \param qs_env ...
    2166              : !> \param bs_env ...
    2167              : !> \param M_pq optimized RI fitting metric
    2168              : ! **************************************************************************************************
    2169            4 :    SUBROUTINE compute_M_pq_inv(qs_env, bs_env, M_pq)
    2170              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2171              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2172              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: M_pq
    2173              : 
    2174              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_M_pq_inv'
    2175              : 
    2176              :       INTEGER                                            :: handle, ncol_local, nrow_local, &
    2177              :                                                             RI_size_opt
    2178            4 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
    2179            4 :       REAL(KIND=dp), ALLOCATABLE                         :: eigenval(:), factors(:)
    2180              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    2181            4 :          POINTER                                         :: M_pq_AA_local, M_pq_local
    2182              :       TYPE(cp_fm_type)                                   :: M_pq_AA, work, X_pq
    2183              : 
    2184            4 :       CALL timeset(routineN, handle)
    2185              : 
    2186           16 :       RI_size_opt = SUM(bs_env%auto_ri%sizes_opt_RI)
    2187              :       CALL create_distributed_matrix(qs_env, bs_env%auto_ri%M_pq_inv, &
    2188            4 :                                      RI_size_opt, RI_size_opt)
    2189            4 :       CALL create_distributed_matrix(qs_env, X_pq, RI_size_opt, RI_size_opt)
    2190            4 :       CALL create_distributed_matrix(qs_env, M_pq_AA, RI_size_opt, RI_size_opt)
    2191            4 :       CALL create_distributed_matrix(qs_env, work, RI_size_opt, RI_size_opt)
    2192              :       CALL cp_fm_get_info(M_pq, nrow_local=nrow_local, ncol_local=ncol_local, &
    2193              :                           row_indices=row_indices, col_indices=col_indices, &
    2194            4 :                           local_data=M_pq_local)
    2195            4 :       CALL cp_fm_get_info(M_pq_AA, local_data=M_pq_AA_local)
    2196            4 :       M_pq_AA_local = 0.0_dp
    2197              :       CALL copy_M_pq_AA_blocks(M_pq_local(:nrow_local, :ncol_local), &
    2198              :                                M_pq_AA_local(:nrow_local, :ncol_local), &
    2199              :                                row_indices(:nrow_local), col_indices(:ncol_local), &
    2200            4 :                                bs_env%auto_ri%sizes_opt_RI)
    2201              : 
    2202           12 :       ALLOCATE (eigenval(RI_size_opt))
    2203            4 :       CALL cp_fm_geeig(M_pq, M_pq_AA, X_pq, eigenval, work)
    2204            4 :       CALL compute_optimized_M_pq_inv_factors(eigenval, factors)
    2205            4 :       CALL cp_fm_column_scale(X_pq, factors)
    2206              :       CALL cp_fm_syrk("U", "N", RI_size_opt, 1.0_dp, X_pq, 1, 1, 0.0_dp, &
    2207            4 :                       bs_env%auto_ri%M_pq_inv)
    2208            4 :       CALL cp_fm_uplo_to_full(bs_env%auto_ri%M_pq_inv, work)
    2209              : 
    2210            4 :       DEALLOCATE (eigenval, factors)
    2211            4 :       CALL cp_fm_release(X_pq)
    2212            4 :       CALL cp_fm_release(M_pq_AA)
    2213            4 :       CALL cp_fm_release(work)
    2214              : 
    2215            4 :       CALL timestop(handle)
    2216              : 
    2217            8 :    END SUBROUTINE compute_M_pq_inv
    2218              : 
    2219              : ! **************************************************************************************************
    2220              : !> \brief Computes M_opt and V_opt without constructing M_ref or V_ref.
    2221              : !> \param qs_env ...
    2222              : !> \param bs_env ...
    2223              : ! **************************************************************************************************
    2224            4 :    SUBROUTINE compute_M_pq_inv_and_V_pq(qs_env, bs_env)
    2225              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2226              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2227              : 
    2228              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_M_pq_inv_and_V_pq'
    2229              : 
    2230              :       INTEGER                                            :: handle
    2231            4 :       TYPE(AB_block_list_type), ALLOCATABLE              :: lists(:)
    2232              :       TYPE(cp_fm_type)                                   :: M_pq
    2233              : 
    2234            4 :       CALL timeset(routineN, handle)
    2235              : 
    2236            4 :       CALL build_AB_block_lists(bs_env, lists)
    2237            4 :       CALL initialize_auto_ri_integrals(bs_env)
    2238            4 :       CALL compute_contracted_RI_matrix(qs_env, bs_env, lists, M_pq, .FALSE.)
    2239            4 :       CALL compute_M_pq_inv(qs_env, bs_env, M_pq)
    2240            4 :       CALL cp_fm_release(M_pq)
    2241            4 :       CALL compute_contracted_RI_matrix(qs_env, bs_env, lists, bs_env%auto_ri%V_pq, .TRUE.)
    2242              : 
    2243              :       ! Activate the optimized per-atom RI block sizes for RI-RS.
    2244            4 :       CALL set_optimized_RI_basis_sizes(bs_env)
    2245              : 
    2246            4 :       CALL timestop(handle)
    2247              : 
    2248           20 :    END SUBROUTINE compute_M_pq_inv_and_V_pq
    2249              : 
    2250              : ! **************************************************************************************************
    2251              : !> \brief Computes the atom-local two-center RI metric (P|Q) and the
    2252              : !>        three-center integrals (μν|P) used in Eqs. (3) and (4).
    2253              : !> \param ao_basis ...
    2254              : !> \param ri_basis ...
    2255              : !> \param potential fitting operator used for both integral types
    2256              : !> \param M_PQ two-center metric of the large reference RI basis
    2257              : !> \param Int_3c three-center integrals (μν|P)
    2258              : ! **************************************************************************************************
    2259            8 :    SUBROUTINE compute_int_3c(ao_basis, ri_basis, potential, M_PQ, Int_3c)
    2260              :       TYPE(gto_basis_set_type), INTENT(IN)               :: ao_basis, ri_basis
    2261              :       TYPE(coulomb_operator_type), INTENT(IN)            :: potential
    2262              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2263              :          INTENT(OUT)                                     :: M_PQ
    2264              :       REAL(KIND=dp), INTENT(OUT)                         :: Int_3c(:, :, :)
    2265              : 
    2266              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_int_3c'
    2267              : 
    2268              :       INTEGER                                            :: handle, iset, jset, kset, max_ao_l, &
    2269              :                                                             max_l, max_ri_l, nAO, ncoa, ncob, &
    2270              :                                                             ncoc, nRI, nsgfa, nsgfb, nsgfc, sgfa, &
    2271              :                                                             sgfb, sgfc
    2272            8 :       INTEGER, DIMENSION(:), POINTER                     :: lmax_a, lmax_c, lmin_a, lmin_c, npgf_a, &
    2273            8 :                                                             npgf_c, nsgf_set_a, nsgf_set_c
    2274            8 :       INTEGER, DIMENSION(:, :), POINTER                  :: first_sgf_a, first_sgf_c
    2275            8 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: Int_2c_cart, Int_2c_spherical
    2276            8 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: Int_3c_cart, Int_3c_spherical_all, &
    2277            8 :                                                             Int_3c_spherical_block
    2278              :       REAL(KIND=dp), DIMENSION(3)                        :: center
    2279            8 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: radius_a, radius_b, radius_c
    2280            8 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: pgf_radius_a, pgf_radius_c, sphi_a, &
    2281            8 :                                                             sphi_c, zeta_a, zeta_c
    2282              :       TYPE(coulomb_integral_context_type)                :: context
    2283              : 
    2284            8 :       CALL timeset(routineN, handle)
    2285              : 
    2286              :       CALL get_gto_basis_set(ao_basis, nsgf=nAO, lmax=lmax_a, lmin=lmin_a, &
    2287              :                              npgf=npgf_a, nsgf_set=nsgf_set_a, first_sgf=first_sgf_a, &
    2288            8 :                              pgf_radius=pgf_radius_a, sphi=sphi_a, zet=zeta_a)
    2289              :       CALL get_gto_basis_set(ri_basis, nsgf=nRI, lmax=lmax_c, lmin=lmin_c, &
    2290              :                              npgf=npgf_c, nsgf_set=nsgf_set_c, first_sgf=first_sgf_c, &
    2291            8 :                              pgf_radius=pgf_radius_c, sphi=sphi_c, zet=zeta_c)
    2292            8 :       CPASSERT(nAO > 0 .AND. nRI > 0)
    2293           20 :       max_ao_l = MAXVAL(lmax_a)
    2294          180 :       max_ri_l = MAXVAL(lmax_c)
    2295            8 :       max_l = MAX(max_ao_l, max_ri_l)
    2296            8 :       CALL init_orbital_pointers(MAX(max_l, 2*max_ao_l))
    2297            8 :       CALL init_spherical_harmonics(max_l, -1)
    2298            8 :       IF (potential%potential_type == do_potential_truncated) THEN
    2299            8 :          IF (active_integral_library%coulomb2_library /= library_libint .OR. &
    2300              :              active_integral_library%coulomb3_library /= library_libint) THEN
    2301              :             CALL cp_abort(__LOCATION__, &
    2302              :                           "AUTO_RI TRUNCATED fitting requires LIBINT for "// &
    2303            0 :                           "COULOMB_2C and COULOMB_3C")
    2304              :          END IF
    2305              :       END IF
    2306              :       ! Libint initialization takes the maximum angular momentum of one shell,
    2307              :       ! not the sum of angular momenta entering an integral.
    2308            8 :       CALL coulomb_integral_init(context, max_am_2c=max_ri_l, max_am_3c=max_l)
    2309            8 :       center = 0.0_dp
    2310              : 
    2311           64 :       ALLOCATE (M_PQ(nRI, nRI), Int_3c_spherical_all(nAO, nAO, nRI))
    2312            8 :       M_PQ = 0.0_dp
    2313            8 :       Int_3c_spherical_all = 0.0_dp
    2314              : 
    2315          180 :       DO iset = 1, ri_basis%nset
    2316          172 :          ncoa = npgf_c(iset)*ncoset(lmax_c(iset))
    2317          172 :          sgfa = first_sgf_c(1, iset)
    2318          172 :          nsgfa = nsgf_set_c(iset)
    2319          172 :          radius_a => pgf_radius_c(1:npgf_c(iset), iset)
    2320         4216 :          DO jset = 1, ri_basis%nset
    2321         4036 :             ncob = npgf_c(jset)*ncoset(lmax_c(jset))
    2322         4036 :             sgfb = first_sgf_c(1, jset)
    2323         4036 :             nsgfb = nsgf_set_c(jset)
    2324         4036 :             radius_b => pgf_radius_c(1:npgf_c(jset), jset)
    2325        28252 :             ALLOCATE (Int_2c_cart(ncoa, ncob), Int_2c_spherical(nsgfa, nsgfb))
    2326         4036 :             Int_2c_cart = 0.0_dp
    2327              :             CALL compute_coulomb_2c(context, lmin_c(iset), lmax_c(iset), &
    2328              :                                     lmin_c(jset), lmax_c(jset), &
    2329              :                                     npgf_c(iset), npgf_c(jset), zeta_c(1:npgf_c(iset), iset), &
    2330              :                                     zeta_c(1:npgf_c(jset), jset), &
    2331         4036 :                                     radius_a, radius_b, center, center, Int_2c_cart, potential)
    2332              :             CALL ab_contract(Int_2c_spherical, Int_2c_cart, sphi_c(:, sgfa:), sphi_c(:, sgfb:), &
    2333         4036 :                              ncoa, ncob, nsgfa, nsgfb)
    2334        99308 :             M_PQ(sgfa:sgfa + nsgfa - 1, sgfb:sgfb + nsgfb - 1) = Int_2c_spherical
    2335         4208 :             DEALLOCATE (Int_2c_spherical, Int_2c_cart)
    2336              :          END DO
    2337              :       END DO
    2338              : 
    2339           20 :       DO iset = 1, ao_basis%nset
    2340           12 :          ncoa = npgf_a(iset)*ncoset(lmax_a(iset))
    2341           12 :          sgfa = first_sgf_a(1, iset)
    2342           12 :          nsgfa = nsgf_set_a(iset)
    2343           12 :          radius_a => pgf_radius_a(1:npgf_a(iset), iset)
    2344           40 :          DO jset = 1, ao_basis%nset
    2345           20 :             ncob = npgf_a(jset)*ncoset(lmax_a(jset))
    2346           20 :             sgfb = first_sgf_a(1, jset)
    2347           20 :             nsgfb = nsgf_set_a(jset)
    2348           20 :             radius_b => pgf_radius_a(1:npgf_a(jset), jset)
    2349          540 :             DO kset = 1, ri_basis%nset
    2350          508 :                ncoc = npgf_c(kset)*ncoset(lmax_c(kset))
    2351          508 :                sgfc = first_sgf_c(1, kset)
    2352          508 :                nsgfc = nsgf_set_c(kset)
    2353          508 :                radius_c => pgf_radius_c(1:npgf_c(kset), kset)
    2354            0 :                ALLOCATE (Int_3c_cart(ncoa, ncob, ncoc), &
    2355         4572 :                          Int_3c_spherical_block(nsgfa, nsgfb, nsgfc))
    2356          508 :                Int_3c_cart = 0.0_dp
    2357              :                CALL compute_coulomb_3c(context, lmin_a(iset), lmax_a(iset), &
    2358              :                                        lmin_a(jset), lmax_a(jset), &
    2359              :                                        lmin_c(kset), lmax_c(kset), npgf_a(iset), npgf_a(jset), &
    2360              :                                        npgf_c(kset), zeta_a(1:npgf_a(iset), iset), &
    2361              :                                        zeta_a(1:npgf_a(jset), jset), zeta_c(1:npgf_c(kset), kset), &
    2362              :                                        radius_a, radius_b, radius_c, center, center, center, &
    2363          508 :                                        Int_3c_cart, potential)
    2364              :                CALL abc_contract(Int_3c_spherical_block, Int_3c_cart, &
    2365              :                                  sphi_a(:, sgfa:), sphi_a(:, sgfb:), &
    2366          508 :                                  sphi_c(:, sgfc:), ncoa, ncob, ncoc, nsgfa, nsgfb, nsgfc)
    2367              :                Int_3c_spherical_all(sgfa:sgfa + nsgfa - 1, sgfb:sgfb + nsgfb - 1, &
    2368        21136 :                                     sgfc:sgfc + nsgfc - 1) = Int_3c_spherical_block
    2369          528 :                DEALLOCATE (Int_3c_spherical_block, Int_3c_cart)
    2370              :             END DO
    2371              :          END DO
    2372              :       END DO
    2373              : 
    2374        16540 :       Int_3c(:, :, :) = Int_3c_spherical_all
    2375            8 :       CALL coulomb_integral_cleanup(context)
    2376        78376 :       IF (.NOT. ALL(ieee_is_finite(M_PQ))) THEN
    2377            0 :          CPABORT("AUTO_RI metric contains invalid values")
    2378              :       END IF
    2379        16540 :       IF (.NOT. ALL(ieee_is_finite(Int_3c))) THEN
    2380            0 :          CPABORT("AUTO_RI three-center integrals contain invalid values")
    2381              :       END IF
    2382              : 
    2383            8 :       CALL timestop(handle)
    2384              : 
    2385           16 :    END SUBROUTINE compute_int_3c
    2386              : 
    2387              : ! **************************************************************************************************
    2388              : !> \brief Initializes the integral kernels used by the AUTO_RI optimization.
    2389              : !> \param bs_env ...
    2390              : ! **************************************************************************************************
    2391            8 :    SUBROUTINE initialize_auto_ri_integrals(bs_env)
    2392              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2393              : 
    2394              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'initialize_auto_ri_integrals'
    2395              : 
    2396              :       CHARACTER(LEN=default_path_length)                 :: table_file
    2397              :       INTEGER                                            :: handle, ikind, max_ao_l, max_l, &
    2398              :                                                             max_ri_l, table_lmax, unit_id
    2399              : 
    2400            8 :       CALL timeset(routineN, handle)
    2401              : 
    2402            8 :       CPASSERT(ALLOCATED(bs_env%basis_set_AO))
    2403            8 :       CPASSERT(ALLOCATED(bs_env%basis_set_RI))
    2404            8 :       CPASSERT(SIZE(bs_env%basis_set_AO) == SIZE(bs_env%basis_set_RI))
    2405            8 :       CPASSERT(SIZE(bs_env%basis_set_RI) > 0)
    2406            8 :       max_ao_l = 0
    2407            8 :       max_ri_l = 0
    2408           24 :       DO ikind = 1, SIZE(bs_env%basis_set_RI)
    2409           16 :          CPASSERT(ASSOCIATED(bs_env%basis_set_AO(ikind)%gto_basis_set))
    2410           16 :          CPASSERT(ASSOCIATED(bs_env%basis_set_RI(ikind)%gto_basis_set))
    2411           40 :          max_ao_l = MAX(max_ao_l, MAXVAL(bs_env%basis_set_AO(ikind)%gto_basis_set%lmax))
    2412          368 :          max_ri_l = MAX(max_ri_l, MAXVAL(bs_env%basis_set_RI(ikind)%gto_basis_set%lmax))
    2413              :       END DO
    2414            8 :       max_l = MAX(max_ao_l, max_ri_l)
    2415              :       ! The OS two-center kernel accesses l+1 even without force evaluation.
    2416            8 :       CALL init_orbital_pointers(max_l + 1)
    2417            8 :       CALL init_spherical_harmonics(max_l, -1)
    2418              : 
    2419            8 :       IF (bs_env%ri_metric%potential_type == do_potential_truncated) THEN
    2420              :          ! Cover both the RI-RI two-center and AO-AO-RI three-center kernels.
    2421            8 :          table_lmax = MAX(2*max_ri_l + 1, 2*max_ao_l + max_ri_l)
    2422            8 :          IF (get_lmax_init() < table_lmax) THEN
    2423            2 :             unit_id = -1
    2424            2 :             IF (bs_env%para_env%is_source()) THEN
    2425            1 :                table_file = discover_file(bs_env%ri_metric%filename)
    2426            1 :                CALL open_file(unit_number=unit_id, file_name=TRIM(table_file))
    2427              :             END IF
    2428            2 :             CALL init_t_c_g0(table_lmax, unit_id, bs_env%para_env%mepos, bs_env%para_env)
    2429            2 :             IF (bs_env%para_env%is_source()) CALL close_file(unit_id)
    2430              :          END IF
    2431              :       END IF
    2432              : 
    2433            8 :       CALL timestop(handle)
    2434              : 
    2435            8 :    END SUBROUTINE initialize_auto_ri_integrals
    2436              : 
    2437              : ! **************************************************************************************************
    2438              : !> \brief Computes the atom-pair two-center RI metric matrix (P_A|Q_B).
    2439              : !> \param bs_env ...
    2440              : !> \param atom_A ...
    2441              : !> \param atom_B ...
    2442              : !> \param M_PQ metric block in the large reference RI basis
    2443              : !> \param ri_potential ...
    2444              : ! **************************************************************************************************
    2445           66 :    SUBROUTINE compute_M_PQ_AB(bs_env, atom_A, atom_B, M_PQ, ri_potential)
    2446              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2447              :       INTEGER, INTENT(IN)                                :: atom_A, atom_B
    2448              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2449              :          INTENT(OUT)                                     :: M_PQ
    2450              :       TYPE(libint_potential_type), INTENT(IN), OPTIONAL  :: ri_potential
    2451              : 
    2452              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_M_PQ_AB'
    2453              : 
    2454              :       INTEGER                                            :: atom_1, atom_2, handle, kind_1, kind_2
    2455           66 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: M_PQ_canonical
    2456              :       TYPE(libint_potential_type)                        :: potential
    2457              : 
    2458           66 :       CALL timeset(routineN, handle)
    2459              : 
    2460           66 :       CPASSERT(ASSOCIATED(bs_env%ri_rs%particle_set))
    2461           66 :       CPASSERT(atom_A >= 1 .AND. atom_A <= bs_env%n_atom)
    2462           66 :       CPASSERT(atom_B >= 1 .AND. atom_B <= bs_env%n_atom)
    2463           66 :       IF (atom_A >= atom_B) THEN
    2464              :          atom_1 = atom_A
    2465              :          atom_2 = atom_B
    2466              :       ELSE
    2467           11 :          atom_1 = atom_B
    2468           11 :          atom_2 = atom_A
    2469              :       END IF
    2470           66 :       kind_1 = bs_env%ri_rs%particle_set(atom_1)%atomic_kind%kind_number
    2471           66 :       kind_2 = bs_env%ri_rs%particle_set(atom_2)%atomic_kind%kind_number
    2472              : 
    2473           66 :       IF (PRESENT(ri_potential)) THEN
    2474           39 :          CPASSERT(ri_potential%potential_type == do_potential_truncated)
    2475           39 :          potential = ri_potential
    2476              :       ELSE
    2477              :          potential%potential_type = do_potential_coulomb
    2478              :       END IF
    2479              :       CALL compute_M_PQ( &
    2480              :          basis_a=bs_env%basis_set_RI(kind_1)%gto_basis_set, &
    2481              :          basis_b=bs_env%basis_set_RI(kind_2)%gto_basis_set, &
    2482              :          center_a=bs_env%ri_rs%particle_set(atom_1)%r, &
    2483              :          center_b=bs_env%ri_rs%particle_set(atom_2)%r, &
    2484           66 :          potential=potential, M_PQ=M_PQ_canonical)
    2485              : 
    2486           66 :       IF (atom_A >= atom_B) THEN
    2487           55 :          CALL MOVE_ALLOC(M_PQ_canonical, M_PQ)
    2488              :       ELSE
    2489           44 :          ALLOCATE (M_PQ(SIZE(M_PQ_canonical, 2), SIZE(M_PQ_canonical, 1)))
    2490        74041 :          M_PQ(:, :) = TRANSPOSE(M_PQ_canonical)
    2491           11 :          DEALLOCATE (M_PQ_canonical)
    2492              :       END IF
    2493              : 
    2494           66 :       CALL timestop(handle)
    2495              : 
    2496          132 :    END SUBROUTINE compute_M_PQ_AB
    2497              : 
    2498              : ! **************************************************************************************************
    2499              : !> \brief Computes one contracted two-center RI metric block (P_A|Q_B).
    2500              : !> \param basis_a ...
    2501              : !> \param basis_b ...
    2502              : !> \param center_a ...
    2503              : !> \param center_b ...
    2504              : !> \param potential ...
    2505              : !> \param M_PQ metric block in the large reference RI basis
    2506              : ! **************************************************************************************************
    2507           66 :    SUBROUTINE compute_M_PQ(basis_a, basis_b, center_a, center_b, potential, M_PQ)
    2508              :       TYPE(gto_basis_set_type), POINTER                  :: basis_a, basis_b
    2509              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN)            :: center_a, center_b
    2510              :       TYPE(libint_potential_type), INTENT(IN)            :: potential
    2511              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2512              :          INTENT(OUT)                                     :: M_PQ
    2513              : 
    2514              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_M_PQ'
    2515              : 
    2516              :       INTEGER                                            :: handle
    2517              : 
    2518           66 :       CALL timeset(routineN, handle)
    2519              : 
    2520          264 :       ALLOCATE (M_PQ(basis_a%nsgf, basis_b%nsgf), source=0.0_dp)
    2521           66 :       SELECT CASE (potential%potential_type)
    2522              :       CASE (do_potential_truncated)
    2523              :          CALL int_operators_r12_ab_os(operator_truncated, M_PQ, rab=center_b - center_a, &
    2524              :                                       fba=basis_a, fbb=basis_b, &
    2525          156 :                                       r_cutoff=potential%cutoff_radius, calculate_forces=.FALSE.)
    2526              :       CASE (do_potential_coulomb)
    2527              :          CALL int_operators_r12_ab_os(operator_coulomb, M_PQ, rab=center_b - center_a, &
    2528          108 :                                       fba=basis_a, fbb=basis_b, calculate_forces=.FALSE.)
    2529              :       CASE DEFAULT
    2530           66 :          CPABORT("AUTO_RI two-center metric requires COULOMB or TRUNCATED")
    2531              :       END SELECT
    2532              : 
    2533           66 :       CALL timestop(handle)
    2534              : 
    2535           66 :    END SUBROUTINE compute_M_PQ
    2536              : 
    2537              : ! **************************************************************************************************
    2538              : !> \brief Returns the reference RI basis size of one atom.
    2539              : !> \param bs_env ...
    2540              : !> \param iatom ...
    2541              : !> \return ...
    2542              : ! **************************************************************************************************
    2543          206 :    INTEGER FUNCTION get_ref_RI_size(bs_env, iatom) RESULT(nRI_ref)
    2544              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2545              :       INTEGER, INTENT(IN)                                :: iatom
    2546              : 
    2547              :       INTEGER                                            :: ikind
    2548              : 
    2549          206 :       ikind = bs_env%ri_rs%particle_set(iatom)%atomic_kind%kind_number
    2550          206 :       nRI_ref = bs_env%basis_set_RI(ikind)%gto_basis_set%nsgf
    2551              : 
    2552          206 :    END FUNCTION get_ref_RI_size
    2553              : 
    2554           78 : END MODULE gw_auto_ri_generate_RI_basis
        

Generated by: LCOV version 2.0-1