LCOV - code coverage report
Current view: top level - src - gw_non_periodic_ri_rs.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:21ef868) Lines: 87.0 % 1533 1334
Test Date: 2026-08-14 07:04:57 Functions: 93.8 % 48 45

            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 GW using RI-RS Approximation for molecules
      10              : !> \par History
      11              : !>      04.2026 created [Ritaj Tyagi]
      12              : ! **************************************************************************************************
      13              : 
      14              : MODULE gw_non_periodic_ri_rs
      15              :    USE atomic_kind_types, ONLY: atomic_kind_type, &
      16              :                                 get_atomic_kind_set
      17              :    USE basis_set_types, ONLY: gto_basis_set_type
      18              :    USE cell_types, ONLY: cell_type, &
      19              :                          pbc
      20              :    USE constants_operator, ONLY: operator_coulomb
      21              :    USE cp_blacs_env, ONLY: cp_blacs_env_create, &
      22              :                            cp_blacs_env_release, &
      23              :                            cp_blacs_env_type
      24              :    USE cp_dbcsr_api, ONLY: &
      25              :       dbcsr_add, dbcsr_binary_read, dbcsr_binary_write, dbcsr_copy, dbcsr_create, &
      26              :       dbcsr_deallocate_matrix, dbcsr_distribution_get, dbcsr_distribution_new, &
      27              :       dbcsr_distribution_release, dbcsr_distribution_type, dbcsr_finalize, &
      28              :       dbcsr_get_block_p, dbcsr_filter, dbcsr_get_data_size, dbcsr_get_info, &
      29              :       dbcsr_get_occupation, dbcsr_iterator_blocks_left, dbcsr_iterator_next_block, &
      30              :       dbcsr_iterator_start, dbcsr_iterator_stop, dbcsr_iterator_type, dbcsr_multiply, &
      31              :       dbcsr_p_type, dbcsr_put_block, dbcsr_release, dbcsr_scale, dbcsr_set, dbcsr_type, &
      32              :       dbcsr_type_no_symmetry
      33              :    USE cp_dbcsr_contrib, ONLY: dbcsr_reserve_all_blocks
      34              :    USE cp_dbcsr_operations, ONLY: copy_dbcsr_to_fm, &
      35              :                                   copy_fm_to_dbcsr, &
      36              :                                   dbcsr_deallocate_matrix_set, &
      37              :                                   max_elements_per_block
      38              :    USE cp_files, ONLY: close_file, &
      39              :                        open_file
      40              :    USE cp_fm_basic_linalg, ONLY: cp_fm_scale_and_add, &
      41              :                                  cp_fm_uplo_to_full
      42              :    USE cp_fm_cholesky, ONLY: cp_fm_cholesky_decompose, &
      43              :                              cp_fm_cholesky_invert, &
      44              :                              cp_fm_cholesky_solve
      45              :    USE cp_fm_diag, ONLY: cp_fm_power
      46              :    USE cp_fm_struct, ONLY: cp_fm_struct_create, &
      47              :                            cp_fm_struct_release, &
      48              :                            cp_fm_struct_type
      49              :    USE cp_fm_types, ONLY: cp_fm_create, &
      50              :                           cp_fm_get_info, &
      51              :                           cp_fm_get_submatrix, &
      52              :                           cp_fm_release, &
      53              :                           cp_fm_set_all, &
      54              :                           cp_fm_set_submatrix, &
      55              :                           cp_fm_to_fm, &
      56              :                           cp_fm_type
      57              :    USE cp_log_handling, ONLY: cp_get_default_logger, &
      58              :                               cp_logger_type
      59              :    USE cp_output_handling, ONLY: cp_p_file, &
      60              :                                  cp_print_key_should_output
      61              :    USE gw_integrals, ONLY: build_3c_integral_block_ctx, &
      62              :                            gw_3c_ctx_create, &
      63              :                            gw_3c_ctx_release, &
      64              :                            gw_3c_ctx_type, &
      65              :                            gw_3c_ws_create, &
      66              :                            gw_3c_ws_release, &
      67              :                            gw_3c_ws_type
      68              :    USE gw_large_cell_gamma, ONLY: &
      69              :       Fourier_transform_w_to_t, G_occ_vir, compute_QP_energies, compute_fm_chi_Gamma_freq, &
      70              :       create_fm_W_MIC_time, delete_unnecessary_files, fill_fm_Sigma_c_Gamma_time, fm_write, &
      71              :       multiply_fm_W_MIC_time_with_Minv_Gamma
      72              :    USE gw_utils, ONLY: de_init_bs_env
      73              :    USE input_constants, ONLY: rtp_method_bse
      74              :    USE input_section_types, ONLY: section_vals_type
      75              :    USE kinds, ONLY: default_path_length, &
      76              :                     default_string_length, &
      77              :                     dp, int_4, int_8
      78              :    USE kpoint_coulomb_2c, ONLY: build_2c_coulomb_matrix_kp
      79              :    USE machine, ONLY: m_flush, m_hostnm, &
      80              :                       m_memory_details, m_walltime
      81              :    USE message_passing, ONLY: mp_para_env_type
      82              :    USE mp2_ri_2c, ONLY: RI_2c_integral_mat
      83              :    USE orbital_pointers, ONLY: indco, &
      84              :                                ncoset
      85              :    USE parallel_gemm_api, ONLY: parallel_gemm
      86              :    USE particle_types, ONLY: particle_type
      87              :    USE physcon, ONLY: angstrom
      88              :    USE post_scf_bandstructure_types, ONLY: post_scf_bandstructure_type
      89              :    USE qs_environment_types, ONLY: get_qs_env, &
      90              :                                    qs_environment_type
      91              :    USE qs_kind_types, ONLY: get_qs_kind, &
      92              :                             qs_kind_type
      93              :    USE util, ONLY: sort
      94              : 
      95              : #include "./base/base_uses.f90"
      96              : 
      97              :    IMPLICIT NONE
      98              : 
      99              :    PRIVATE
     100              : 
     101              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'gw_non_periodic_ri_rs'
     102              : 
     103              :    ! DBCSR ships each ranks matrix panel in one MPI message whose length is stored in a
     104              :    ! 32-bit default INTEGER (dbcsr_mpiwrap.F: msglen = SIZE(buffer)). Exceeding this ceiling
     105              :    ! overflows the count to a negative/garbage value and multiply_cannon segfaults. This is
     106              :    ! that hard limit in elements (= HUGE(int_4) = 2^31-1), the same bound DBCSR uses
     107              :    ! internally as mp_max_memory_size. Panel sizing keeps every per-rank message below it.
     108              : 
     109              :    INTEGER(KIND=int_8), PARAMETER, PRIVATE :: dbcsr_msg_elem_limit = INT(HUGE(0_int_4), int_8)
     110              : 
     111              :    PUBLIC :: gw_calc_non_periodic_ri_rs, ri_rs_grid_assembler, &
     112              :              get_basis_offsets, precompute_ri_rs_radii, solve_D_lp_distributed, &
     113              :              atomic_basis_at_grid_point, compute_coeff_Z_lP
     114              : 
     115              : CONTAINS
     116              : 
     117              : ! **************************************************************************************************
     118              : !> \brief GW calculation using RI-RS formalism for molecules
     119              : !> \param qs_env ...
     120              : !> \param bs_env ...
     121              : ! **************************************************************************************************
     122           20 :    SUBROUTINE gw_calc_non_periodic_ri_rs(qs_env, bs_env)
     123              : 
     124              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     125              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     126              : 
     127              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'gw_calc_non_periodic_ri_rs'
     128              : 
     129              :       INTEGER                                            :: handle
     130           20 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_Sigma_x_Gamma, fm_W_time
     131           20 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :)  :: fm_Sigma_c_Gamma_time
     132              : 
     133           20 :       CALL timeset(routineN, handle)
     134              : 
     135              :       ! ========================================================================
     136              :       ! 0. Precompute AO and RI radii
     137              :       !    Per-atom cutoff radii from the most diffuse Gaussian primitives of
     138              :       !    the AO ("ORB") and RI auxiliary ("RI_AUX") basis sets:
     139              :       !      α_min,ao = min { ζ_ao | ζ_ao > 10⁻³ },  α_min,ri analogous
     140              :       !      r_ao = sqrt( -ln(ε) / α_min,ao )   (radius_ao_per_atom)
     141              :       !      r_ri = sqrt( -ln(ε) / α_min,ri )   (radius_ri_per_atom)
     142              :       ! ========================================================================
     143           20 :       CALL precompute_ri_rs_radii(qs_env, bs_env)
     144              : 
     145              :       ! ========================================================================
     146              :       ! 1. Grid generation for RI-RS
     147              :       !    Modified Lebedev atomic grids (Duchemin & Blase), one per atom,
     148              :       !    concatenated into a flat global list:  r_l = R_A + r_l^(A)
     149              :       ! ========================================================================
     150           20 :       CALL ri_rs_grid_assembler(qs_env, bs_env, bs_env%ri_rs%grid_points)
     151              : 
     152              :       ! ========================================================================
     153              :       ! 2a. Atomic basis evaluation on the grid (grid x AO matrix)
     154              :       !     Φ_μl = Φ_μ(r_l)   (mat_phi_mu_l)
     155              :       ! ========================================================================
     156              :       CALL atomic_basis_at_grid_point(qs_env, bs_env, bs_env%ri_rs%grid_points, &
     157           20 :                                       bs_env%ri_rs%mat_phi_mu_l)
     158              : 
     159              :       ! ========================================================================
     160              :       ! 2b. Print the memory estimate for the RI-RS calculation
     161              :       ! ========================================================================
     162           20 :       CALL print_ri_rs_memory_estimate(qs_env, bs_env)
     163              : 
     164              :       ! ========================================================================
     165              :       ! 3. RI-RS fitting coefficients Z_lP (grid x RI matrix)
     166              :       !    Per-atom regularized solve, restricted to grid points r_l within a
     167              :       !    cutoff distance of atom P:
     168              :       !      a. D_ll' = [ Σ_μ Φ_μ(r_l) Φ_μ(r_l') ]²
     169              :       !      b. D_lP  = Σ_μν Φ_μ(r_l) Φ_ν(r_l) (μν|P)
     170              :       !      c. Jacobi conditioning with d_l = 1/sqrt(D_ll):
     171              :       !         D'_ll' = d_l D_ll' d_l' + λδ_ll' ,  D'_lP = d_l D_lP
     172              :       !      d. Solve Σ_l' D'_ll' Z'_l'P = D'_lP
     173              :       !      e. Rescale Z_lP = d_l Z'_lP                        (mat_Z_lP)
     174              :       ! ========================================================================
     175              :       CALL compute_coeff_Z_lP(qs_env, bs_env, bs_env%ri_rs%grid_points, &
     176           20 :                               bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP)
     177              :       ! flag the RI-RS grid as built so a subsequent RT-BSE run reuses Z_lP instead of rebuilding it
     178           20 :       bs_env%ri_rs%grid_built = .TRUE.
     179              : 
     180              :       ! ========================================================================
     181              :       ! 4. Polarizability matrix χ on the imaginary-time grid
     182              :       !      G^occ_µλ(i|τ|)  = Σ_n^occ C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
     183              :       !      G^vir_µλ(i|τ|)  = Σ_n^vir C_µn e^(-|(ϵ_n-ϵ_F)τ|) C_λn
     184              :       !      G^occ_ll'(i|τ|) = Σ_µν Φ_µ(r_l) G^occ_µν Φ_ν(r_l')  (G^vir analogous)
     185              :       !      χ_ll'(iτ)       = G^occ_ll'(i|τ|) ∘ G^vir_ll'(i|τ|) (element-wise)
     186              :       !      χ_PQ(iτ)        = Σ_ll' Z_lP χ_ll'(iτ) Z_l'Q
     187              :       ! ========================================================================
     188              :       CALL get_mat_chi_Gamma_tau(bs_env, bs_env%mat_chi_Gamma_tau, &
     189           20 :                                  bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP)
     190              : 
     191              :       ! ========================================================================
     192              :       ! 5. Screened Coulomb interaction W (RI basis)
     193              :       !      χ_PQ(iτ) -> χ_PQ(iω) -> ε_PQ(iω) -> W_PQ(iω) -> W_PQ(iτ)
     194              :       ! ========================================================================
     195           20 :       CALL compute_W(bs_env, qs_env, bs_env%mat_chi_Gamma_tau, fm_W_time)
     196              : 
     197              :       ! ========================================================================
     198              :       ! 6. Exact-exchange self-energy Σ^x
     199              :       !      D_µν          = Σ_n^occ C_µn C_νn                (density matrix)
     200              :       !      D_ll'         = Σ_µν Φ_µ(r_l) D_µν Φ_ν(r_l')
     201              :       !      V^tr_ll'      = Σ_PQ Z_lP V^tr_PQ Z_l'Q          (truncated Coulomb)
     202              :       !      Σ^x_ll'       = D_ll' ∘ V^tr_ll'
     203              :       !      Σ^x_λσ(k=0)   = -Σ_ll' Φ_λ(r_l) Σ^x_ll' Φ_σ(r_l')
     204              :       ! ========================================================================
     205              :       CALL compute_Sigma_x(bs_env, qs_env, bs_env%ri_rs%mat_phi_mu_l, &
     206           20 :                            bs_env%ri_rs%mat_Z_lP, fm_Sigma_x_Gamma)
     207              : 
     208              :       ! ========================================================================
     209              :       ! 7. Correlation self-energy Σ^c on the imaginary-time grid
     210              :       !      W_ll'(iτ)     =  Σ_PQ Z_lP W^MIC_PQ(iτ) Z_l'Q
     211              :       !      Σ^c_ll'(iτ)   = -G^occ_ll'(i|τ|) ∘ W_ll'(iτ),  τ < 0
     212              :       !      Σ^c_ll'(iτ)   =  G^vir_ll'(i|τ|) ∘ W_ll'(iτ),  τ > 0
     213              :       !      Σ^c_λσ(iτ)    =  Σ_ll' Φ_λ(r_l) Σ^c_ll'(iτ) Φ_σ(r_l')
     214              :       ! ========================================================================
     215              :       CALL compute_Sigma_c(bs_env, fm_W_time, bs_env%ri_rs%mat_phi_mu_l, &
     216           20 :                            bs_env%ri_rs%mat_Z_lP, fm_Sigma_c_Gamma_time)
     217              : 
     218              :       ! ========================================================================
     219              :       ! 8. Quasiparticle energies (analytic continuation iτ -> iω -> real ϵ)
     220              :       !      Σ^c_λσ(iτ) -> Σ^c_nn(ϵ)
     221              :       !      ϵ_n^GW = ϵ_n^DFT + Σ^c_nn(ϵ_n^GW) + Σ^x_nn - v^xc_nn
     222              :       ! ========================================================================
     223           20 :       CALL compute_QP_energies(bs_env, qs_env, fm_Sigma_x_Gamma, fm_Sigma_c_Gamma_time)
     224              : 
     225           20 :       CALL de_init_bs_env(qs_env, bs_env)
     226              : 
     227           20 :       CALL timestop(handle)
     228              : 
     229           20 :    END SUBROUTINE gw_calc_non_periodic_ri_rs
     230              : 
     231              : ! **************************************************************************************************
     232              : !> \brief Compute per-atom AO and RI basis radii from the most diffuse Gaussian
     233              : !>        primitive in the AO ("ORB") and RI auxiliary ("RI_AUX") basis sets.
     234              : !>        Stores results in bs_env%ri_rs%radius_ao_per_atom(:) and
     235              : !>        bs_env%ri_rs%radius_ri_per_atom(:) and prints a per-atom table.
     236              : !>        Radius:  r_kind = sqrt(-log(eps) / alpha_min_kind)
     237              : !>        with eps = eps_filter.
     238              : !> \param qs_env ...
     239              : !> \param bs_env ...
     240              : ! **************************************************************************************************
     241           28 :    SUBROUTINE precompute_ri_rs_radii(qs_env, bs_env)
     242              : 
     243              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     244              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     245              : 
     246              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'precompute_ri_rs_radii'
     247              : 
     248              :       INTEGER                                            :: handle, i, iatom, ikind, j, natom, nkind
     249           28 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: kind_of
     250              :       REAL(KIND=dp)                                      :: eps
     251           28 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: alpha_min_ao_kind, alpha_min_ri_kind
     252           28 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: zet_ao, zet_ri
     253           28 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     254           28 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     255              : 
     256           28 :       CALL timeset(routineN, handle)
     257              : 
     258              :       CALL get_qs_env(qs_env, nkind=nkind, atomic_kind_set=atomic_kind_set, &
     259           28 :                       particle_set=particle_set)
     260           28 :       natom = SIZE(particle_set)
     261              : 
     262           28 :       eps = bs_env%eps_filter
     263              : 
     264          112 :       ALLOCATE (alpha_min_ao_kind(nkind), alpha_min_ri_kind(nkind))
     265           66 :       alpha_min_ao_kind = HUGE(1.0_dp)
     266           66 :       alpha_min_ri_kind = HUGE(1.0_dp)
     267              : 
     268           66 :       DO ikind = 1, nkind
     269           38 :          zet_ao => bs_env%basis_set_AO(ikind)%gto_basis_set%zet
     270           38 :          zet_ri => bs_env%basis_set_RI(ikind)%gto_basis_set%zet
     271              : 
     272          120 :          DO i = 1, SIZE(zet_ao, 1)
     273          286 :             DO j = 1, SIZE(zet_ao, 2)
     274          248 :                IF (zet_ao(i, j) > 1.0E-3_dp) then
     275          166 :                   alpha_min_ao_kind(ikind) = MIN(alpha_min_ao_kind(ikind), zet_ao(i, j))
     276              :                END IF
     277              :             END DO
     278              :          END DO
     279          104 :          DO i = 1, SIZE(zet_ri, 1)
     280          310 :             DO j = 1, SIZE(zet_ri, 2)
     281          272 :                IF (zet_ri(i, j) > 1.0E-3_dp) then
     282          234 :                   alpha_min_ri_kind(ikind) = MIN(alpha_min_ri_kind(ikind), zet_ri(i, j))
     283              :                END IF
     284              :             END DO
     285              :          END DO
     286              :       END DO
     287              : 
     288           28 :       CALL get_atomic_kind_set(atomic_kind_set=atomic_kind_set, kind_of=kind_of)
     289              : 
     290           84 :       ALLOCATE (bs_env%ri_rs%radius_ao_per_atom(natom))
     291           56 :       ALLOCATE (bs_env%ri_rs%radius_ri_per_atom(natom))
     292           94 :       DO iatom = 1, natom
     293           66 :          ikind = kind_of(iatom)
     294           66 :          bs_env%ri_rs%radius_ao_per_atom(iatom) = SQRT(-LOG(eps)/alpha_min_ao_kind(ikind))
     295           94 :          bs_env%ri_rs%radius_ri_per_atom(iatom) = SQRT(-LOG(eps)/alpha_min_ri_kind(ikind))
     296              :       END DO
     297              : 
     298           28 :       IF (bs_env%unit_nr > 0) THEN
     299           14 :          WRITE (bs_env%unit_nr, '(T2,A)') 'Per-kind RI-RS basis radii (Å):'
     300           14 :          WRITE (bs_env%unit_nr, '(T4,A6,2X,A4,2A14)') 'Kind', 'Elem', 'r_AO (Å)', 'r_RI (Å)'
     301           33 :          DO ikind = 1, nkind
     302              :             WRITE (bs_env%unit_nr, '(T4,I6,2X,A4,2F14.4)') &
     303           19 :                ikind, &
     304           19 :                atomic_kind_set(ikind)%element_symbol, &
     305           19 :                SQRT(-LOG(eps)/alpha_min_ao_kind(ikind))*angstrom, &
     306           52 :                SQRT(-LOG(eps)/alpha_min_ri_kind(ikind))*angstrom
     307              :          END DO
     308           14 :          WRITE (bs_env%unit_nr, '(A)') ' '
     309              :       END IF
     310              : 
     311           28 :       DEALLOCATE (alpha_min_ao_kind, alpha_min_ri_kind, kind_of)
     312              : 
     313           28 :       CALL timestop(handle)
     314              : 
     315           28 :    END SUBROUTINE precompute_ri_rs_radii
     316              : 
     317              : ! **************************************************************************************************
     318              : !> \brief Spreads the low 21 bits of a into every third bit (bits 0,3,6,...,60): the 1-D helper
     319              : !>        for a 3-D Morton (Z-order) code. Standard 64-bit magic-mask implementation.
     320              : !> \param a value in [0, 2^21)
     321              : !> \param x a with two zero bits inserted between consecutive input bits
     322              : ! **************************************************************************************************
     323          198 :    SUBROUTINE morton_split3(a, x)
     324              :       INTEGER(KIND=int_8), INTENT(IN)                    :: a
     325              :       INTEGER(KIND=int_8), INTENT(OUT)                   :: x
     326              : 
     327          198 :       x = IAND(a, INT(z'1FFFFF', int_8))
     328          198 :       x = IAND(IOR(x, ISHFT(x, 32)), INT(z'1F00000000FFFF', int_8))
     329          198 :       x = IAND(IOR(x, ISHFT(x, 16)), INT(z'1F0000FF0000FF', int_8))
     330          198 :       x = IAND(IOR(x, ISHFT(x, 8)), INT(z'100F00F00F00F00F', int_8))
     331          198 :       x = IAND(IOR(x, ISHFT(x, 4)), INT(z'10C30C30C30C30C3', int_8))
     332          198 :       x = IAND(IOR(x, ISHFT(x, 2)), INT(z'1249249249249249', int_8))
     333          198 :    END SUBROUTINE morton_split3
     334              : 
     335              : ! **************************************************************************************************
     336              : !> \brief Returns a permutation of atom indices in Morton (Z-order) space-filling order of their
     337              : !>        Cartesian centers, so consecutive atoms are spatial neighbors. The RI-RS grid rows are
     338              : !>        laid down in this order, so a contiguous grid panel maps to a compact spatial region and
     339              : !>        the CUTOFF_RADIUS_RL_W neighborhood of every panel shrinks. The grid row index
     340              : !>        is a summed contraction index, so ANY permutation is result-preserving; this one is
     341              : !>        chosen purely to improve locality. Coordinates are normalized to the atom bounding box
     342              : !>        and quantized to 21 bits per axis (sub-picometre for any real cell).
     343              : !> \param particle_set ...
     344              : !> \param order order(i) = atom index placed at layout position i
     345              : ! **************************************************************************************************
     346           28 :    SUBROUTINE spatial_atom_order(particle_set, order)
     347              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     348              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: order
     349              : 
     350              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'spatial_atom_order'
     351              :       INTEGER, PARAMETER                                 :: nbits = 21
     352              : 
     353              :       INTEGER                                            :: handle, ia, k, natom
     354              :       INTEGER(KIND=int_8)                                :: cmax, ic(3), m1, m2, m3
     355           28 :       INTEGER(KIND=int_8), ALLOCATABLE                   :: mcode(:)
     356              :       REAL(KIND=dp)                                      :: hi(3), lo(3), span(3)
     357              : 
     358           28 :       CALL timeset(routineN, handle)
     359              : 
     360           28 :       natom = SIZE(particle_set)
     361          140 :       ALLOCATE (order(natom), mcode(natom))
     362              :       cmax = ISHFT(1_int_8, nbits) - 1_int_8
     363              : 
     364          112 :       lo(:) = HUGE(1.0_dp)
     365          112 :       hi(:) = -HUGE(1.0_dp)
     366           94 :       DO ia = 1, natom
     367          292 :          DO k = 1, 3
     368          198 :             lo(k) = MIN(lo(k), particle_set(ia)%r(k))
     369          264 :             hi(k) = MAX(hi(k), particle_set(ia)%r(k))
     370              :          END DO
     371              :       END DO
     372          112 :       span(:) = hi(:) - lo(:)
     373          112 :       DO k = 1, 3
     374          112 :          IF (span(k) <= 0.0_dp) span(k) = 1.0_dp
     375              :       END DO
     376              : 
     377           94 :       DO ia = 1, natom
     378          264 :          DO k = 1, 3
     379          198 :             ic(k) = INT(((particle_set(ia)%r(k) - lo(k))/span(k))*REAL(cmax, dp), int_8)
     380          264 :             ic(k) = MIN(cmax, MAX(0_int_8, ic(k)))
     381              :          END DO
     382           66 :          CALL morton_split3(ic(1), m1)
     383           66 :          CALL morton_split3(ic(2), m2)
     384           66 :          CALL morton_split3(ic(3), m3)
     385           94 :          mcode(ia) = IOR(IOR(m1, ISHFT(m2, 1)), ISHFT(m3, 2))
     386              :       END DO
     387              : 
     388              :       ! sort(mcode, natom, order): order(i) = original atom index with the i-th smallest code
     389           28 :       CALL sort(mcode, natom, order)
     390              : 
     391           28 :       DEALLOCATE (mcode)
     392              : 
     393           28 :       CALL timestop(handle)
     394              : 
     395           28 :    END SUBROUTINE spatial_atom_order
     396              : 
     397              : ! **************************************************************************************************
     398              : !> \brief Compute grid points for RI-RS
     399              : !>        Right now based on Ivan and Xavier implementation
     400              : !>        JCP 150, 174120 (2019), JCTC 17, 2383 (2021)
     401              : !> \param qs_env ...
     402              : !> \param bs_env ...
     403              : !> \param ri_rs_grid_points ...
     404              : ! **************************************************************************************************
     405           28 :    SUBROUTINE ri_rs_grid_assembler(qs_env, bs_env, ri_rs_grid_points)
     406              : 
     407              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     408              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     409              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: ri_rs_grid_points(:, :)
     410              : 
     411              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'ri_rs_grid_assembler'
     412              : 
     413              :       INTEGER                                            :: atom_idx, end_idx, handle, i_layout, &
     414              :                                                             ikind, j, natom, nkind, start_idx, &
     415              :                                                             total_grid_npts
     416           28 :       INTEGER, ALLOCATABLE                               :: atom_order(:), atom_to_kind(:), &
     417           28 :                                                             ri_rs_grid_offsets(:)
     418              :       REAL(KIND=dp)                                      :: atomic_center(3)
     419           28 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     420           28 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     421              : 
     422           28 :       CALL timeset(routineN, handle)
     423              : 
     424              :        !! Get the information about the atoms in the system
     425           28 :       CALL get_qs_env(qs_env, atomic_kind_set=atomic_kind_set, particle_set=particle_set)
     426              : 
     427           28 :       nkind = SIZE(atomic_kind_set)
     428           28 :       natom = SIZE(particle_set)
     429              : 
     430              :        !! 1. Build the grid cache
     431           28 :       CALL build_grid_cache(bs_env, atomic_kind_set)
     432              : 
     433              :        !! 2. Calculate grid counts and offsets
     434           84 :       ALLOCATE (ri_rs_grid_offsets(natom + 1))
     435           84 :       ALLOCATE (atom_to_kind(natom))
     436              : 
     437              :       ! grid_atom_boundaries(k) = 1-based start of the k-th atom's grid run in LAYOUT order
     438              :       ! (the order in which the points are laid down below). Used later to build atom-aligned
     439              :       ! DBCSR grid row-blocks in atomic_basis_at_grid_point.
     440           28 :       IF (ALLOCATED(bs_env%ri_rs%grid_atom_boundaries)) DEALLOCATE (bs_env%ri_rs%grid_atom_boundaries)
     441           56 :       ALLOCATE (bs_env%ri_rs%grid_atom_boundaries(natom + 1))
     442              : 
     443              :       ! atom -> kind map (needed before the spatial layout loop, which visits atoms in a
     444              :       ! kind-independent order).
     445           66 :       DO ikind = 1, nkind
     446          132 :          DO j = 1, SIZE(atomic_kind_set(ikind)%atom_list)
     447          104 :             atom_to_kind(atomic_kind_set(ikind)%atom_list(j)) = ikind
     448              :          END DO
     449              :       END DO
     450              : 
     451              :       ! Lay the grid rows down in Morton (spatial) atom order rather than kind-major, so a
     452              :       ! contiguous grid panel maps to a compact spatial region and the CUTOFF_RADIUS_RL_W
     453              :       ! neighborhood width of every panel shrinks. The grid row index is summed over in χ/Σ,
     454              :       ! so this reordering is result-preserving (see spatial_atom_order).
     455           28 :       CALL spatial_atom_order(particle_set, atom_order)
     456              : 
     457           28 :       total_grid_npts = 0
     458           94 :       DO i_layout = 1, natom
     459           66 :          atom_idx = atom_order(i_layout)
     460           66 :          ikind = atom_to_kind(atom_idx)
     461           66 :          ri_rs_grid_offsets(atom_idx) = total_grid_npts + 1
     462           66 :          bs_env%ri_rs%grid_atom_boundaries(i_layout) = total_grid_npts + 1
     463           94 :          total_grid_npts = total_grid_npts + bs_env%ri_rs%grid_cache(ikind)%npts
     464              :       END DO
     465              : 
     466           28 :       ri_rs_grid_offsets(natom + 1) = total_grid_npts + 1
     467           28 :       bs_env%ri_rs%grid_atom_boundaries(natom + 1) = total_grid_npts + 1
     468              : 
     469           28 :       IF (bs_env%unit_nr > 0) THEN
     470              :          WRITE (bs_env%unit_nr, FMT="(T2,A,T69,I12)") &
     471           14 :             'Total grid points used for RI-RS:', total_grid_npts
     472           14 :          WRITE (bs_env%unit_nr, "(A)") ' '
     473              :       END IF
     474              : 
     475              :        !! 3. Allocate the global ri_rs_grid arrays
     476           84 :       ALLOCATE (ri_rs_grid_points(3, total_grid_npts))
     477              : 
     478              :        !! 4. Parallelize the grid generation loop
     479              :       !$OMP PARALLEL DO DEFAULT(NONE) &
     480              :       !$OMP SHARED(ri_rs_grid_points, ri_rs_grid_offsets, atom_to_kind, &
     481              :       !$OMP        particle_set, bs_env, natom) &
     482              :       !$OMP PRIVATE(atom_idx, ikind, atomic_center, start_idx, end_idx) &
     483           28 :       !$OMP SCHEDULE(DYNAMIC, 1)
     484              :       DO atom_idx = 1, natom
     485              :          ikind = atom_to_kind(atom_idx)
     486              :          atomic_center(:) = particle_set(atom_idx)%r(:)
     487              : 
     488              :          start_idx = ri_rs_grid_offsets(atom_idx)
     489              :          end_idx = start_idx + bs_env%ri_rs%grid_cache(ikind)%npts - 1
     490              : 
     491              :          !! Shift the cached origin grid by the atom's center
     492              :          ri_rs_grid_points(1, start_idx:end_idx) = bs_env%ri_rs%grid_cache(ikind)%raw_points(1, :) + atomic_center(1)
     493              :          ri_rs_grid_points(2, start_idx:end_idx) = bs_env%ri_rs%grid_cache(ikind)%raw_points(2, :) + atomic_center(2)
     494              :          ri_rs_grid_points(3, start_idx:end_idx) = bs_env%ri_rs%grid_cache(ikind)%raw_points(3, :) + atomic_center(3)
     495              : 
     496              :       END DO
     497              :       !$OMP END PARALLEL DO
     498              : 
     499              :        !! 5. Cleanup memory
     500           28 :       IF (ALLOCATED(bs_env%ri_rs%grid_cache)) THEN
     501           66 :          DO ikind = 1, nkind
     502           66 :             IF (ALLOCATED(bs_env%ri_rs%grid_cache(ikind)%raw_points)) DEALLOCATE (bs_env%ri_rs%grid_cache(ikind)%raw_points)
     503              :          END DO
     504           66 :          DEALLOCATE (bs_env%ri_rs%grid_cache)
     505              :       END IF
     506              : 
     507           28 :       DEALLOCATE (atom_order, atom_to_kind, ri_rs_grid_offsets)
     508              : 
     509           28 :       CALL timestop(handle)
     510              : 
     511           56 :    END SUBROUTINE ri_rs_grid_assembler
     512              : 
     513              : ! **************************************************************************************************
     514              : !> \brief Reads grids from .ion files and stores them in memory based on grid_select
     515              : !> \param bs_env ...
     516              : !> \param atomic_kind_set ...
     517              : ! **************************************************************************************************
     518           28 :    SUBROUTINE build_grid_cache(bs_env, atomic_kind_set)
     519              : 
     520              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     521              :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     522              : 
     523              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'build_grid_cache'
     524              : 
     525              :       CHARACTER(LEN=default_path_length)                 :: filename, full_path, line
     526              :       CHARACTER(LEN=default_string_length)               :: atom_sym, suffix
     527              :       INTEGER                                            :: colon_idx, handle, i, ierr, ikind, &
     528              :                                                             iunit, nkind
     529              :       REAL(KIND=dp)                                      :: pt(3)
     530              : 
     531           28 :       CALL timeset(routineN, handle)
     532              : 
     533              :       ! Determine the file suffix based on the user's choice
     534           28 :       IF (bs_env%ri_rs%grid_select == 1) THEN
     535           22 :          suffix = "_def2-tzvp-rs.ion"
     536            6 :       ELSE IF (bs_env%ri_rs%grid_select == 2) THEN
     537            4 :          suffix = "_cc-pvtz-rs.ion"
     538            2 :       ELSE IF (bs_env%ri_rs%grid_select == 3) THEN
     539            2 :          IF (LEN_TRIM(bs_env%ri_rs%grid_file_suffix) > 0) THEN
     540            2 :             suffix = TRIM(bs_env%ri_rs%grid_file_suffix)
     541              :          ELSE
     542            0 :             suffix = "_rirs.ion"
     543              :          END IF
     544              :       ELSE
     545            0 :          CPABORT("Unknown grid_select (1=def2-TZVPP, 2=cc-pVTZ, 3=user-provided).")
     546              :       END IF
     547              : 
     548           28 :       nkind = SIZE(atomic_kind_set)
     549          122 :       IF (.NOT. ALLOCATED(bs_env%ri_rs%grid_cache)) ALLOCATE (bs_env%ri_rs%grid_cache(nkind))
     550              : 
     551           66 :       DO ikind = 1, nkind
     552           38 :          atom_sym = TRIM(atomic_kind_set(ikind)%element_symbol)
     553           38 :          filename = TRIM(atom_sym)//TRIM(suffix)
     554              : 
     555           38 :          full_path = "ri_rs_grid/"//TRIM(filename)
     556              : 
     557              :          CALL open_file(file_name=TRIM(full_path), unit_number=iunit, &
     558           38 :                         file_action="READ", file_status="OLD")
     559              : 
     560              :          ! Parse the preamble for 'n points'
     561           38 :          bs_env%ri_rs%grid_cache(ikind)%npts = 0
     562              :          DO
     563         1284 :             READ (iunit, '(A)', IOSTAT=ierr) line
     564         1284 :             IF (ierr /= 0) EXIT
     565         1284 :             IF (INDEX(line, 'n points') > 0) THEN
     566           38 :                colon_idx = INDEX(line, ':')
     567           38 :                READ (line(colon_idx + 1:), *) bs_env%ri_rs%grid_cache(ikind)%npts
     568           38 :                EXIT
     569              :             END IF
     570              :          END DO
     571              : 
     572              :          ! Allocate the cache array for this specific element
     573          114 :          ALLOCATE (bs_env%ri_rs%grid_cache(ikind)%raw_points(3, bs_env%ri_rs%grid_cache(ikind)%npts))
     574              : 
     575           38 :          REWIND (iunit)
     576              :          DO
     577         1360 :             READ (iunit, '(A)', IOSTAT=ierr) line
     578         1360 :             IF (ierr /= 0) EXIT
     579         1360 :             IF (INDEX(line, '<grid_points>') > 0) EXIT
     580              :          END DO
     581              : 
     582              :          ! Read the raw grid coordinates
     583         7730 :          DO i = 1, bs_env%ri_rs%grid_cache(ikind)%npts
     584         7692 :             READ (iunit, *, IOSTAT=ierr) pt(1), pt(2), pt(3)
     585         7692 :             IF (ierr /= 0) THEN
     586            0 :                CPABORT("Unexpected EOF in grid file ")
     587              :             END IF
     588        30806 :             bs_env%ri_rs%grid_cache(ikind)%raw_points(:, i) = pt(:)
     589              :          END DO
     590              : 
     591           66 :          CALL close_file(unit_number=iunit)
     592              :       END DO
     593              : 
     594           28 :       CALL timestop(handle)
     595              : 
     596           28 :    END SUBROUTINE build_grid_cache
     597              : 
     598              : ! **************************************************************************************************
     599              : !> \brief Evaluates the AO basis on the RI-RS grid and stores it as the sparse DBCSR matrix
     600              : !>        Φ_μl = Φ_μ(r_l)  (rows = grid points in atom-aligned blocks of at most
     601              : !>        max_elements_per_block points, columns = one block per atom's full AO set).
     602              : !>        Grid points outside the reach of an atom's most
     603              : !>        diffuse Gaussian (or the CUTOFF_RADIUS_RL_AO) are skipped, and only blocks
     604              : !>        with at least one element > eps_filter are stored. This locality is the source of
     605              : !>        ALL grid-dimension sparsity used downstream. Also caches the atom centers and the
     606              : !>        per-chunk centroids needed by the optional CUTOFF_RADIUS_G_W / CUTOFF_RADIUS_RL_W
     607              : !>        operator truncations.
     608              : !> \param qs_env ...
     609              : !> \param bs_env ...
     610              : !> \param ri_rs_grid_points ...
     611              : !> \param mat_phi_mu_l ...
     612              : ! **************************************************************************************************
     613           28 :    SUBROUTINE atomic_basis_at_grid_point(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l)
     614              : 
     615              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     616              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     617              :       REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT)          :: ri_rs_grid_points(:, :)
     618              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_phi_mu_l
     619              : 
     620              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'atomic_basis_at_grid_point'
     621              : 
     622              :       INTEGER :: bs_eff, c_size, dimen_ORB, handle, i, i_blk, ia, iatom, natom, npcol, nprow, &
     623              :                  num_grid_chunks, r_end, r_start, remaining, run, safe_max, total_grid_npts
     624           28 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: blk_row_start, first_sgf
     625           28 :       INTEGER, DIMENSION(:), POINTER                     :: c_blk_sizes, col_dist, &
     626           28 :                                                             r_blk_sizes, row_dist
     627              :       REAL(KIND=dp)                                      :: r2_threshold
     628           28 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: atom_col_buffer
     629           28 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     630              :       TYPE(cell_type), POINTER                           :: cell
     631              :       TYPE(dbcsr_distribution_type)                      :: dist
     632              :       TYPE(dbcsr_distribution_type), POINTER             :: dbcsr_dist_ks
     633              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     634           28 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     635           28 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     636              : 
     637           28 :       CALL timeset(routineN, handle)
     638              : 
     639              :       ! Extract environment variables
     640              :       CALL get_qs_env(qs_env, cell=cell, atomic_kind_set=atomic_kind_set, &
     641              :                       qs_kind_set=qs_kind_set, particle_set=particle_set, &
     642           28 :                       para_env=para_env)
     643              : 
     644           28 :       natom = SIZE(particle_set)
     645           28 :       total_grid_npts = SIZE(ri_rs_grid_points, 2)
     646              : 
     647              :       ! Map the starting indices of spherical gaussian functions (SGF) for each atom
     648           84 :       ALLOCATE (first_sgf(natom + 1))
     649           28 :       CALL get_basis_offsets(particle_set, qs_kind_set, first_sgf, dimen_ORB)
     650              : 
     651              :       ! =========================================================================
     652              :       ! 1. SETUP DBCSR MATRIX TOPOLOGY
     653              :       ! =========================================================================
     654              : 
     655              :       ! A. Define Column Block Sizes (1 Block = 1 Atom's full basis set)
     656           84 :       ALLOCATE (c_blk_sizes(natom))
     657           94 :       DO iatom = 1, natom
     658           94 :          c_blk_sizes(iatom) = first_sgf(iatom + 1) - first_sgf(iatom)
     659              :       END DO
     660              : 
     661              :       ! B. Define Row Block Sizes: atom-aligned blocks (a block never spans two atoms' grid
     662              :       ! runs), each atom's run subdivided into blocks of at most bs_eff points.
     663              : 
     664              :       ! Fetch CP2K's default process grid configuration
     665           28 :       CALL get_qs_env(qs_env, dbcsr_dist=dbcsr_dist_ks)
     666           28 :       CALL dbcsr_distribution_get(dbcsr_dist_ks, nprows=nprow, npcols=npcol)
     667              : 
     668              :       ! Overflow-safe upper bound on the block size (see dbcsr_msg_elem_limit).
     669              :       safe_max = INT(0.5_dp*REAL(dbcsr_msg_elem_limit, dp)*REAL(MAX(MIN(nprow, npcol), 1), dp)/ &
     670           28 :                      REAL(total_grid_npts, dp))
     671           28 :       safe_max = MAX(1, safe_max)
     672              :       ! Block size = CP2K's global max_elements_per_block (GLOBAL/DBCSR input; default 32),
     673              :       ! overflow-capped.
     674           28 :       bs_eff = MAX(1, MIN(max_elements_per_block, safe_max))
     675              : 
     676              :       ! Count the atom-aligned blocks, then fill r_blk_sizes and each block's starting grid row.
     677           28 :       num_grid_chunks = 0
     678           94 :       DO ia = 1, natom
     679           66 :          run = bs_env%ri_rs%grid_atom_boundaries(ia + 1) - bs_env%ri_rs%grid_atom_boundaries(ia)
     680           94 :          IF (run > 0) num_grid_chunks = num_grid_chunks + (run + bs_eff - 1)/bs_eff
     681              :       END DO
     682          112 :       ALLOCATE (r_blk_sizes(num_grid_chunks), blk_row_start(num_grid_chunks))
     683           28 :       i_blk = 0
     684           28 :       r_start = 1
     685           94 :       DO ia = 1, natom
     686           66 :          remaining = bs_env%ri_rs%grid_atom_boundaries(ia + 1) - bs_env%ri_rs%grid_atom_boundaries(ia)
     687          516 :          DO WHILE (remaining > 0)
     688          422 :             i_blk = i_blk + 1
     689          422 :             r_blk_sizes(i_blk) = MIN(bs_eff, remaining)
     690          422 :             blk_row_start(i_blk) = r_start
     691          422 :             r_start = r_start + r_blk_sizes(i_blk)
     692          488 :             remaining = remaining - r_blk_sizes(i_blk)
     693              :          END DO
     694              :       END DO
     695              : 
     696           28 :       IF (bs_env%unit_nr > 0) THEN
     697           14 :          WRITE (bs_env%unit_nr, '(T2,A,T69,I12)') 'RI-RS grid row-blocks of phi(mu,l)', num_grid_chunks
     698           14 :          WRITE (bs_env%unit_nr, '(T2,A,T69,I12)') 'RI-RS grid points per block (max)', bs_eff
     699              :       END IF
     700              : 
     701              :       ! Cache atomic positions: AO and RI blocks are one-block-per-atom, so these are the
     702              :       ! block centers used by the optional CUTOFF_RADIUS_G_W atom-pair truncation.
     703           28 :       IF (ALLOCATED(bs_env%ri_rs%atom_centers)) DEALLOCATE (bs_env%ri_rs%atom_centers)
     704           84 :       ALLOCATE (bs_env%ri_rs%atom_centers(3, natom))
     705           94 :       DO iatom = 1, natom
     706          556 :          bs_env%ri_rs%atom_centers(1:3, iatom) = particle_set(iatom)%r(1:3)
     707              :       END DO
     708              : 
     709              :       ! Cache per-chunk centroids for the optional CUTOFF_RADIUS_RL_W block truncation.
     710           28 :       IF (bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp) THEN
     711            2 :          IF (ALLOCATED(bs_env%ri_rs%chunk_centroids)) DEALLOCATE (bs_env%ri_rs%chunk_centroids)
     712            6 :          ALLOCATE (bs_env%ri_rs%chunk_centroids(3, num_grid_chunks))
     713           44 :          DO i_blk = 1, num_grid_chunks
     714           42 :             r_start = blk_row_start(i_blk)
     715           42 :             r_end = r_start + r_blk_sizes(i_blk) - 1
     716              :             bs_env%ri_rs%chunk_centroids(1, i_blk) = &
     717         1258 :                SUM(ri_rs_grid_points(1, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
     718              :             bs_env%ri_rs%chunk_centroids(2, i_blk) = &
     719         1258 :                SUM(ri_rs_grid_points(2, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
     720              :             bs_env%ri_rs%chunk_centroids(3, i_blk) = &
     721         1260 :                SUM(ri_rs_grid_points(3, r_start:r_end))/REAL(r_blk_sizes(i_blk), dp)
     722              :          END DO
     723              :       END IF
     724              : 
     725              :       ! C. Build Custom Mappings using Round-Robin across the 2D process grid
     726              : 
     727           56 :       ALLOCATE (row_dist(num_grid_chunks))
     728          450 :       DO i = 1, num_grid_chunks
     729          450 :          row_dist(i) = MOD(i - 1, nprow)
     730              :       END DO
     731              : 
     732           56 :       ALLOCATE (col_dist(natom))
     733           94 :       DO i = 1, natom
     734           94 :          col_dist(i) = MOD(i - 1, npcol)
     735              :       END DO
     736              : 
     737              :       ! E. Create the DBCSR Distribution and Initialize the Matrix
     738              :       CALL dbcsr_distribution_new(dist, template=dbcsr_dist_ks, &
     739           28 :                                   row_dist=row_dist, col_dist=col_dist)
     740              : 
     741              :       CALL dbcsr_create(mat_phi_mu_l, name="phi_val_sparse", dist=dist, &
     742              :                         matrix_type=dbcsr_type_no_symmetry, &
     743           28 :                         row_blk_size=r_blk_sizes, col_blk_size=c_blk_sizes)
     744              : 
     745              :       ! =========================================================================
     746              :       ! 2. STREAM DATA DIRECTLY INTO SPARSE MATRIX
     747              :       ! =========================================================================
     748              :       ! Iterate over the atoms assigned to this specific MPI rank
     749           28 :       DO iatom = para_env%mepos + 1, natom, para_env%num_pe
     750              : 
     751           33 :          c_size = c_blk_sizes(iatom)
     752              : 
     753              :          ! Allocate a temporary dense buffer just for this specific atom
     754          132 :          ALLOCATE (atom_col_buffer(total_grid_npts, c_size))
     755           33 :          atom_col_buffer = 0.0_dp
     756              : 
     757              :          ! Evaluate the basis functions on the grid. Skip grid points outside
     758              :          ! the spatial extent of the most diffuse AO Gaussian on iatom; beyond
     759              :          ! that radius the contribution is guaranteed below eps_filter. A positive
     760              :          ! CUTOFF_RADIUS_RL_AO overrides this with a user-defined hard cutoff.
     761           33 :          IF (bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
     762            3 :             r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
     763              :          ELSE
     764           30 :             r2_threshold = bs_env%ri_rs%radius_ao_per_atom(iatom)**2
     765              :          END IF
     766              :          CALL fill_phi_for_atom(atom_col_buffer, ri_rs_grid_points, total_grid_npts, &
     767           33 :                                 iatom, particle_set, qs_kind_set, cell, r2_threshold)
     768              : 
     769              :          ! Slice the dense column into the atom-aligned grid row-blocks and insert into DBCSR
     770          560 :          DO i_blk = 1, num_grid_chunks
     771          527 :             r_start = blk_row_start(i_blk)
     772          527 :             r_end = r_start + r_blk_sizes(i_blk) - 1
     773              : 
     774              :             ! Apply dynamic sparsity filtering: Only store blocks with physical significance
     775        58123 :             IF (MAXVAL(ABS(atom_col_buffer(r_start:r_end, 1:c_size))) > bs_env%eps_filter) THEN
     776              :                CALL dbcsr_put_block(mat_phi_mu_l, row=i_blk, col=iatom, &
     777          515 :                                     block=atom_col_buffer(r_start:r_end, 1:c_size))
     778              :             END IF
     779              :          END DO
     780              : 
     781           33 :          DEALLOCATE (atom_col_buffer)
     782              : 
     783              :       END DO
     784              : 
     785           28 :       CALL dbcsr_finalize(mat_phi_mu_l)
     786              : 
     787           28 :       CALL print_matrix_occupation(mat_phi_mu_l, 'φ(μ,l)', para_env, bs_env%unit_nr)
     788              : 
     789              :       ! -------------------------------------------------------------------------
     790              :       ! CLEANUP
     791              :       ! -------------------------------------------------------------------------
     792           28 :       DEALLOCATE (first_sgf, r_blk_sizes, c_blk_sizes, row_dist, col_dist, blk_row_start)
     793           28 :       CALL dbcsr_distribution_release(dist)
     794              : 
     795           28 :       CALL timestop(handle)
     796              : 
     797          140 :    END SUBROUTINE atomic_basis_at_grid_point
     798              : 
     799              : ! **************************************************************************************************
     800              : !> \brief Evaluates all spherical AO basis functions of one atom on a set of grid points and
     801              : !>        ACCUMULATES them into phi_val (+=). For each point within the cutoff radius,
     802              : !>          Φ_μ(r) = Σ_pgf Σ_cart sphi(cart,μ) · (x-X_A)^lx (y-Y_A)^ly (z-Z_A)^lz · e^(-ζ_pgf |r-R_A|²)
     803              : !>        i.e. contracted Cartesian Gaussians transformed to the spherical basis via the sphi
     804              : !>        coefficients. Distances are minimum-image wrapped (pbc); points with
     805              : !>        |r - R_A|² > r2_threshold are skipped since every primitive is below eps there.
     806              : !>        OMP-parallel over grid points.
     807              : !> \param phi_val ...
     808              : !> \param ri_rs_grid ...
     809              : !> \param npts ...
     810              : !> \param iatom ...
     811              : !> \param particle_set ...
     812              : !> \param qs_kind_set ...
     813              : !> \param cell ...
     814              : !> \param r2_threshold ...
     815              : ! **************************************************************************************************
     816          105 :    SUBROUTINE fill_phi_for_atom(phi_val, ri_rs_grid, npts, iatom, &
     817              :                                 particle_set, qs_kind_set, cell, r2_threshold)
     818              : 
     819              :       REAL(KIND=dp), INTENT(INOUT)                       :: phi_val(:, :)
     820              :       INTEGER, INTENT(IN)                                :: npts
     821              :       REAL(KIND=dp), INTENT(IN)                          :: ri_rs_grid(3, npts)
     822              :       INTEGER, INTENT(IN)                                :: iatom
     823              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     824              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     825              :       TYPE(cell_type), POINTER                           :: cell
     826              :       REAL(KIND=dp), INTENT(IN)                          :: r2_threshold
     827              : 
     828              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'fill_phi_for_atom'
     829              : 
     830              :       INTEGER                                            :: first_sgf, handle, i_pt, ico, iend_co, &
     831              :                                                             ikind, ipgf, iset, isgf, ishell, &
     832              :                                                             istart_co, l, last_sgf, lx, ly, lz, &
     833              :                                                             n_cart_total, row_idx
     834              :       REAL(KIND=dp)                                      :: alpha, dist_vec(3), exp_val, poly, r2, &
     835              :                                                             r_atom(3), weight
     836              :       TYPE(gto_basis_set_type), POINTER                  :: orb_basis_set
     837              : 
     838          105 :       CALL timeset(routineN, handle)
     839              : 
     840              :       ! Get Atom Info
     841          105 :       ikind = particle_set(iatom)%atomic_kind%kind_number
     842          105 :       CALL get_qs_kind(qs_kind_set(ikind), basis_set=orb_basis_set, basis_type="ORB")
     843          105 :       IF (.NOT. ASSOCIATED(orb_basis_set)) THEN
     844            0 :          CALL timestop(handle)
     845            0 :          RETURN
     846              :       END IF
     847              : 
     848          420 :       r_atom = particle_set(iatom)%r
     849              : 
     850              :       !$OMP PARALLEL DO DEFAULT(NONE) &
     851              :       !$OMP SHARED(phi_val, ri_rs_grid, npts, orb_basis_set, r_atom, cell, ncoset, indco, &
     852              :       !$OMP        r2_threshold) &
     853              :       !$OMP PRIVATE(i_pt, dist_vec, r2, iset, n_cart_total, ishell, l, istart_co, iend_co, &
     854              :       !$OMP         first_sgf, last_sgf, ipgf, alpha, exp_val, isgf, ico, row_idx, weight, &
     855              :       !$OMP         lx, ly, lz, poly) &
     856          105 :       !$OMP SCHEDULE(STATIC)
     857              :       DO i_pt = 1, npts
     858              :          dist_vec = pbc(ri_rs_grid(:, i_pt) - r_atom, cell)
     859              :          r2 = DOT_PRODUCT(dist_vec, dist_vec)
     860              :          IF (r2 > r2_threshold) CYCLE
     861              : 
     862              :          DO iset = 1, orb_basis_set%nset
     863              :             n_cart_total = ncoset(orb_basis_set%lmax(iset))
     864              : 
     865              :             DO ishell = 1, orb_basis_set%nshell(iset)
     866              :                l = orb_basis_set%l(ishell, iset)
     867              :                istart_co = ncoset(l - 1) + 1
     868              :                iend_co = ncoset(l)
     869              : 
     870              :                first_sgf = orb_basis_set%first_sgf(ishell, iset)
     871              :                last_sgf = orb_basis_set%last_sgf(ishell, iset)
     872              : 
     873              :                DO ipgf = 1, orb_basis_set%npgf(iset)
     874              :                   alpha = orb_basis_set%zet(ipgf, iset)
     875              :                   exp_val = EXP(-alpha*r2)
     876              : 
     877              :                   DO isgf = first_sgf, last_sgf
     878              :                      DO ico = istart_co, iend_co
     879              :                         row_idx = (ipgf - 1)*n_cart_total + ico
     880              :                         weight = orb_basis_set%sphi(row_idx, isgf)
     881              :                         lx = indco(1, ico)
     882              :                         ly = indco(2, ico)
     883              :                         lz = indco(3, ico)
     884              :                         poly = (dist_vec(1)**lx)*(dist_vec(2)**ly)*(dist_vec(3)**lz)
     885              : 
     886              :                         phi_val(i_pt, isgf) = phi_val(i_pt, isgf) + (weight*poly*exp_val)
     887              : 
     888              :                      END DO
     889              :                   END DO
     890              :                END DO
     891              :             END DO
     892              :          END DO
     893              :       END DO
     894              :       !$OMP END PARALLEL DO
     895              : 
     896          105 :       CALL timestop(handle)
     897              : 
     898              :    END SUBROUTINE fill_phi_for_atom
     899              : 
     900              : ! **************************************************************************************************
     901              : !> \brief Computes the AO basis offsets: first_sgf(iatom) is the global index of the first
     902              : !>        spherical Gaussian function (SGF) of iatom, first_sgf(natom+1) = total_sgf + 1,
     903              : !>        and total_sgf is the total number of AO basis functions.
     904              : !> \param particle_set ...
     905              : !> \param qs_kind_set ...
     906              : !> \param first_sgf ...
     907              : !> \param total_sgf ...
     908              : ! **************************************************************************************************
     909           28 :    SUBROUTINE get_basis_offsets(particle_set, qs_kind_set, first_sgf, total_sgf)
     910              : 
     911              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     912              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     913              :       INTEGER, INTENT(OUT)                               :: first_sgf(:), total_sgf
     914              : 
     915              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'get_basis_offsets'
     916              : 
     917              :       INTEGER                                            :: handle, iatom, ikind, nsgf
     918              : 
     919           28 :       CALL timeset(routineN, handle)
     920              : 
     921           28 :       total_sgf = 0
     922           94 :       DO iatom = 1, SIZE(particle_set)
     923           66 :          first_sgf(iatom) = total_sgf + 1
     924           66 :          ikind = particle_set(iatom)%atomic_kind%kind_number
     925           66 :          CALL get_qs_kind(qs_kind_set(ikind), nsgf=nsgf, basis_type="ORB")
     926           94 :          total_sgf = total_sgf + nsgf
     927              :       END DO
     928           28 :       first_sgf(SIZE(particle_set) + 1) = total_sgf + 1
     929              : 
     930           28 :       CALL timestop(handle)
     931              : 
     932           28 :    END SUBROUTINE get_basis_offsets
     933              : 
     934              : ! **************************************************************************************************
     935              : !> \brief Computes the RI-RS fitting coefficients Z_lP by solving, independently for every RI
     936              : !>        atom P, a Jacobi-conditioned, Tikhonov-regularized linear system restricted to the
     937              : !>        grid points r_l inside P's integration sphere |r_l - R_P| <= cutoff_ri(P):
     938              : !>          D_ll'  = [ Σ_μ Φ_μ(r_l) Φ_μ(r_l') ]²             (squared grid Gram matrix, Eq. 13)
     939              : !>          D_lP   = Σ_μν Φ_μ(r_l) Φ_ν(r_l) (μν|P)           (grid-RI right-hand side, Eq. 15)
     940              : !>          d_l    = 1 / sqrt(D_ll)                          (Jacobi conditioning vector)
     941              : !>          D'_ll' = d_l D_ll' d_l' + λ δ_ll'                (λ = TIKHONOV_SIGMA regularization)
     942              : !>          Σ_l' D'_ll' Z'_l'P = d_l D_lP                    (Cholesky solve, Eq. 14)
     943              : !>          Z_lP   = d_l Z'_l'P                              (undo the conditioning)
     944              : !>        Work is distributed over atoms in two phases (planned by classify_z_lp_atoms and
     945              : !>        lpt_assign_atoms): Phase A solves "small" atoms with single-rank LAPACK
     946              : !>        (dpotrf/dpotrs); Phase B solves "big" atoms, whose dense Gram matrix would exceed one
     947              : !>        rank's memory, with ScaLAPACK (pdpotrf/pdpotrs) over rank subgroups of size G.
     948              : !>        The solved Z columns are scattered into the sparse global mat_Z_lP.
     949              : !>        If a Z_lP restart file exists, it is read instead and the solve is skipped entirely.
     950              : !> \param qs_env ...
     951              : !> \param bs_env ...
     952              : !> \param ri_rs_grid_points ...
     953              : !> \param mat_phi_mu_l ...
     954              : !> \param mat_Z_lP ...
     955              : ! **************************************************************************************************
     956           56 :    SUBROUTINE compute_coeff_Z_lP(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     957              : 
     958              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     959              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     960              :       REAL(KIND=dp), ALLOCATABLE, INTENT(INOUT)          :: ri_rs_grid_points(:, :)
     961              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     962              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     963              : 
     964              :       CHARACTER(LEN=*), PARAMETER :: key = 'PROPERTIES%BANDSTRUCTURE%GW%PRINT%RESTART', &
     965              :                                      routineN = 'compute_coeff_Z_lP'
     966              : 
     967              :       INTEGER :: atom_j_mepos, atom_j_stride, atom_P, G, handle, handle_dpotrf, handle_dpotrs, &
     968              :                  i_blk, idx, info, iphase, j, max_ao_size, my_group, n_ao_total, n_big, n_done, &
     969              :                  n_groups, n_loc_ri, n_local_grid, n_my_atoms, n_small, natom, next_pct, &
     970              :                  npcol_phi, num_grid_chunks, P_loop_atom, phase_hi
     971           56 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: big_list, local_grid_idx, &
     972           28 :                                                             my_atoms_A, my_atoms_B, &
     973           28 :                                                             n_local_grid_atom, row_offset, small_list
     974           56 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist_ri, r_blk_sizes, &
     975           28 :                                                             ri_blk_sizes, row_dist_grid
     976              :       LOGICAL                                            :: do_scatter, use_dist
     977              :       REAL(KIND=dp)                                      :: balance_A, balance_B, cutoff_ri, r_c, t1
     978           28 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cutoff_ri_per_atom, d_vec_local
     979           28 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: D_local, d_lp_local, phi_local
     980           28 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     981              :       TYPE(cell_type), POINTER                           :: cell
     982              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env_sub
     983              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_b, fm_struct_D
     984              :       TYPE(cp_fm_type)                                   :: fm_b, fm_D
     985              :       TYPE(cp_logger_type), POINTER                      :: logger
     986              :       TYPE(dbcsr_distribution_type)                      :: dist_phi, dist_Z
     987          364 :       TYPE(gw_3c_ctx_type)                               :: ctx_3c
     988              :       TYPE(mp_para_env_type), POINTER                    :: para_env, para_env_sub
     989           28 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     990           28 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     991              :       TYPE(section_vals_type), POINTER                   :: input
     992              : 
     993           28 :       CALL timeset(routineN, handle)
     994              : 
     995           28 :       t1 = m_walltime()
     996              : 
     997              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, input=input, &
     998           28 :                       qs_kind_set=qs_kind_set, cell=cell, atomic_kind_set=atomic_kind_set)
     999              : 
    1000           28 :       NULLIFY (para_env_sub, blacs_env_sub)
    1001              : 
    1002           28 :       natom = SIZE(bs_env%i_RI_start_from_atom)
    1003           28 :       n_ao_total = bs_env%i_ao_end_from_atom(natom)
    1004              : 
    1005              :       ! =========================================================================
    1006              :       ! 1. SETUP DBCSR TOPOLOGY & EXACT OFFSETS
    1007              :       !    mat_Z_lP inherits the grid row blocking (and row distribution) of
    1008              :       !    mat_phi_mu_l; its columns are one block per RI atom.
    1009              :       ! =========================================================================
    1010           28 :       CALL dbcsr_get_info(mat_phi_mu_l, row_blk_size=r_blk_sizes, distribution=dist_phi)
    1011           28 :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist_grid, npcols=npcol_phi)
    1012              : 
    1013           28 :       num_grid_chunks = SIZE(r_blk_sizes)
    1014              : 
    1015           84 :       ALLOCATE (row_offset(num_grid_chunks))
    1016           28 :       row_offset(1) = 0
    1017          422 :       DO i_blk = 2, num_grid_chunks
    1018          422 :          row_offset(i_blk) = row_offset(i_blk - 1) + r_blk_sizes(i_blk - 1)
    1019              :       END DO
    1020              : 
    1021          112 :       ALLOCATE (ri_blk_sizes(natom), col_dist_ri(natom))
    1022           94 :       DO atom_P = 1, natom
    1023           66 :          ri_blk_sizes(atom_P) = bs_env%i_RI_end_from_atom(atom_P) - bs_env%i_RI_start_from_atom(atom_P) + 1
    1024           94 :          col_dist_ri(atom_P) = MOD(atom_P - 1, npcol_phi)
    1025              :       END DO
    1026              : 
    1027           28 :       CALL dbcsr_distribution_new(dist_Z, template=dist_phi, row_dist=row_dist_grid, col_dist=col_dist_ri)
    1028              : 
    1029           28 :       IF (bs_env%ri_rs%Z_lP_exists) THEN
    1030              :          CALL dbcsr_binary_read(filepath=TRIM(bs_env%prefix)//"Z_lP.matrix", &
    1031              :                                 distribution=dist_Z, &
    1032            2 :                                 matrix_new=mat_Z_lP)
    1033            2 :          IF (bs_env%unit_nr > 0) THEN
    1034              :             WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
    1035            1 :                'Read Z_lP from file ', ' Execution time', m_walltime() - t1, ' s'
    1036              :             ! The grid rows are laid out in Morton (spatial) order (spatial_atom_order); a Z_lP.matrix
    1037              :             ! written by an older build with a different grid ordering would be silently misread into
    1038              :             ! the current row order. Delete stale Z_lP.matrix files and recompute if in doubt.
    1039              :             WRITE (bs_env%unit_nr, '(T2,A)') &
    1040            1 :                '*** NOTE: Z_lP restart must match the current (spatial) grid row ordering ***'
    1041            1 :             WRITE (bs_env%unit_nr, '(A)') ' '
    1042              :          END IF
    1043              :       ELSE
    1044              : 
    1045              :          CALL dbcsr_create(mat_Z_lP, name="mat_Z_lP", dist=dist_Z, &
    1046              :                            matrix_type=dbcsr_type_no_symmetry, &
    1047           26 :                            row_blk_size=r_blk_sizes, col_blk_size=ri_blk_sizes)
    1048              : 
    1049              :          ! Largest per-atom AO block, needed to size the 3c-integral work buffers.
    1050           26 :          max_ao_size = 0
    1051           86 :          DO j = 1, SIZE(bs_env%i_ao_start_from_atom)
    1052           86 :             max_ao_size = MAX(max_ao_size, bs_env%i_ao_end_from_atom(j) - bs_env%i_ao_start_from_atom(j) + 1)
    1053              :          END DO
    1054              : 
    1055              :          ! Per-atom RI-RS integration sphere:
    1056              :          !   cutoff_ri(P) = r_c + r_RI(P)
    1057              :          ! where r_c is the truncated-Coulomb cutoff of the RI metric and r_RI the radius of
    1058              :          ! the most diffuse RI auxiliary Gaussian on P. The CUTOFF_RADIUS_RL_RI keyword
    1059              :          ! (when > 0) overrides the entire cutoff calculation.
    1060           78 :          ALLOCATE (cutoff_ri_per_atom(natom))
    1061              : 
    1062           26 :          IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
    1063            8 :             cutoff_ri_per_atom(:) = bs_env%ri_rs%cutoff_radius_ri_rs
    1064              :          ELSE
    1065           24 :             r_c = bs_env%ri_metric%cutoff_radius
    1066           78 :             DO P_loop_atom = 1, natom
    1067              :                cutoff_ri_per_atom(P_loop_atom) = &
    1068           78 :                   r_c + bs_env%ri_rs%radius_ri_per_atom(P_loop_atom)
    1069              :             END DO
    1070              :          END IF
    1071              : 
    1072           26 :          CALL print_sphere_cutoff_table(bs_env, atomic_kind_set, particle_set, cutoff_ri_per_atom)
    1073              : 
    1074              :          ! =========================================================================
    1075              :          ! 2. PER-ATOM SOLVER CLASSIFICATION
    1076              :          !    Split the atoms into "small" (single-rank LAPACK, Phase A) and "big"
    1077              :          !    (distributed ScaLAPACK over subgroups of G ranks, Phase B) by comparing
    1078              :          !    each atom's estimated solve peak memory against the measured budget.
    1079              :          ! =========================================================================
    1080              :          CALL classify_z_lp_atoms(bs_env, para_env, ri_rs_grid_points, particle_set, &
    1081              :                                   cutoff_ri_per_atom, ri_blk_sizes, n_ao_total, &
    1082           26 :                                   n_local_grid_atom, small_list, n_small, big_list, n_big, G)
    1083              : 
    1084              :          ! LPT scheduling: sort the atoms of each phase by estimated solve cost
    1085              :          ! (n_local_grid^3, Cholesky-dominated) and greedily assign to the least-loaded
    1086              :          ! rank (Phase A) / subgroup (Phase B).
    1087              :          CALL lpt_assign_atoms(small_list, n_small, n_local_grid_atom, para_env%num_pe, &
    1088           26 :                                para_env%mepos, my_atoms_A, balance_A)
    1089           26 :          IF (n_big > 0) THEN
    1090            0 :             n_groups = para_env%num_pe/G
    1091            0 :             my_group = MIN(para_env%mepos/G, n_groups - 1)
    1092              :             CALL lpt_assign_atoms(big_list, n_big, n_local_grid_atom, n_groups, my_group, &
    1093            0 :                                   my_atoms_B, balance_B)
    1094              :          ELSE
    1095           26 :             ALLOCATE (my_atoms_B(0))
    1096           26 :             balance_B = 1.0_dp
    1097              :          END IF
    1098              : 
    1099              :          ! Atoms this rank will process across both phases for rank-0 progress
    1100           26 :          n_my_atoms = SIZE(my_atoms_A) + SIZE(my_atoms_B)
    1101           26 :          n_done = 0
    1102           26 :          next_pct = 25
    1103              : 
    1104           26 :          IF (bs_env%unit_nr > 0) THEN
    1105              :             WRITE (bs_env%unit_nr, '(T2,A,I7,A,I8,A)') &
    1106           13 :                'RI-RS Z_lP solver: ', n_small, ' atoms single-rank (BLAS), ', n_big, &
    1107           26 :                ' atoms distributed'
    1108           13 :             IF (n_small > 0) WRITE (bs_env%unit_nr, '(T4,A,F18.2)') &
    1109           13 :                'estimated single-rank load balance (max/mean cost per rank)', balance_A
    1110           13 :             IF (n_big > 0) THEN
    1111              :                WRITE (bs_env%unit_nr, '(T4,A,I44,A)') &
    1112            0 :                   'distributed subgroup size G', G, ' ranks'
    1113              :                WRITE (bs_env%unit_nr, '(T4,A,F17.2)') &
    1114            0 :                   'estimated distributed load balance (max/mean cost per group)', balance_B
    1115              :             END IF
    1116           13 :             WRITE (bs_env%unit_nr, '(A)') ' '
    1117              :          END IF
    1118              : 
    1119              :          ! Shared context for the three-center integrals (μν|P) of the RHS build
    1120              :          CALL gw_3c_ctx_create(ctx_3c, qs_env, bs_env%ri_metric, &
    1121              :                                basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
    1122           26 :                                basis_i=bs_env%basis_set_RI)
    1123              : 
    1124              :          ! =========================================================================
    1125              :          ! 3. TWO-PHASE LOOP OVER ATOMS
    1126              :          !    Phase A processes the "small" atoms with the single-rank BLAS path
    1127              :          !    Phase B processes the "big" atoms with the distributed ScaLAPACK path
    1128              :          !    over rank subgroups of size G. phi_local for each atom's cutoff sphere
    1129              :          !    is built on the fly to avoid replicating a global grid x AO matrix.
    1130              :          ! =========================================================================
    1131           78 :          DO iphase = 1, 2
    1132           52 :             IF (iphase == 1) THEN
    1133           26 :                use_dist = .FALSE.
    1134           26 :                atom_j_mepos = 0
    1135           26 :                atom_j_stride = 1
    1136           26 :                phase_hi = SIZE(my_atoms_A)
    1137              :             ELSE
    1138           26 :                IF (n_big == 0) CYCLE
    1139            0 :                use_dist = .TRUE.
    1140            0 :                n_groups = para_env%num_pe/G
    1141            0 :                my_group = MIN(para_env%mepos/G, n_groups - 1)
    1142            0 :                ALLOCATE (para_env_sub)
    1143            0 :                CALL para_env_sub%from_split(para_env, my_group)
    1144            0 :                CALL cp_blacs_env_create(blacs_env=blacs_env_sub, para_env=para_env_sub)
    1145            0 :                atom_j_mepos = para_env_sub%mepos
    1146            0 :                atom_j_stride = para_env_sub%num_pe
    1147              :                ! All ranks of a subgroup share my_group, hence the identical my_atoms_B list
    1148              :                ! (the per-atom ScaLAPACK solve is collective over the subgroup).
    1149            0 :                phase_hi = SIZE(my_atoms_B)
    1150              :             END IF
    1151              : 
    1152           56 :             DO idx = 1, phase_hi
    1153           30 :                IF (iphase == 1) THEN
    1154           30 :                   atom_P = my_atoms_A(idx)
    1155              :                ELSE
    1156            0 :                   atom_P = my_atoms_B(idx)
    1157              :                END IF
    1158              : 
    1159           30 :                n_loc_ri = ri_blk_sizes(atom_P)
    1160           30 :                cutoff_ri = cutoff_ri_per_atom(atom_P)
    1161              : 
    1162              :                ! ---------------------------------------------------------------------
    1163              :                ! A. Sphere-local AO matrix Φ_μ(r_l): select the grid points with
    1164              :                !    |r_l - R_P| <= cutoff_ri(P), evaluate every AO on them, and drop
    1165              :                !    points whose largest AO amplitude is below EPS_FILTER.
    1166              :                ! ---------------------------------------------------------------------
    1167              :                CALL build_phi_on_sphere(bs_env, particle_set, qs_kind_set, cell, &
    1168              :                                         ri_rs_grid_points, atom_P, cutoff_ri, n_ao_total, &
    1169           30 :                                         local_grid_idx, n_local_grid, phi_local)
    1170              : 
    1171              :                ! ---------------------------------------------------------------------
    1172              :                ! B. Right-hand side  D_lP = Σ_μν Φ_μ(r_l) Φ_ν(r_l) (μν|P)
    1173              :                ! ---------------------------------------------------------------------
    1174          120 :                ALLOCATE (d_lp_local(n_local_grid, n_loc_ri))
    1175           30 :                d_lp_local = 0.0_dp
    1176              : 
    1177              :                CALL compute_d_lp(bs_env, ctx_3c, phi_local, d_lp_local, n_local_grid, &
    1178           30 :                                  n_loc_ri, atom_P, max_ao_size, atom_j_mepos, atom_j_stride)
    1179              : 
    1180              :                ! Reduce per-subgroup-rank partials into the replicated d_lp_local.
    1181              :                ! Skipped for BLAS path: each rank has the full sum locally.
    1182           30 :                IF (use_dist) THEN
    1183            0 :                   CALL para_env_sub%sum(d_lp_local)
    1184              :                END IF
    1185              : 
    1186              :                ! ---------------------------------------------------------------------
    1187              :                ! C. Jacobi conditioning vector d_l = 1/sqrt(D_ll) and, on the BLAS path,
    1188              :                !    the dense conditioned Gram matrix D'_ll' = d_l D_ll' d_l' + λδ_ll'.
    1189              :                ! ---------------------------------------------------------------------
    1190           90 :                ALLOCATE (d_vec_local(n_local_grid))
    1191              : 
    1192           30 :                IF (.NOT. use_dist) THEN
    1193              :                   CALL build_gram_jacobi_blas(phi_local, n_local_grid, n_ao_total, &
    1194           30 :                                               bs_env%ri_rs%tikhonov, D_local, d_vec_local)
    1195              :                ELSE
    1196              :                   ! ScaLAPACK path: only d_vec is needed here (= 1/||phi(r_l)||^2);
    1197              :                   ! solve_D_lp_distributed builds its block-cyclic slice of D' internally
    1198              :                   ! with the squared+scaled values, so no dense D_local on this rank.
    1199              :                   CALL build_jacobi_diag_from_phi(phi_local, n_local_grid, n_ao_total, &
    1200            0 :                                                   d_vec_local)
    1201              :                END IF
    1202              : 
    1203              :                ! ---------------------------------------------------------------------
    1204              :                ! D. Pre-scale the RHS:  D'_lP = d_l * D_lP
    1205              :                ! ---------------------------------------------------------------------
    1206           30 :                CALL scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
    1207              : 
    1208              :                ! ---------------------------------------------------------------------
    1209              :                ! E. Cholesky solve  Σ_l' D'_ll' Z'_l'P = D'_lP
    1210              :                !    (BLAS dpotrf/dpotrs or ScaLAPACK pdpotrf/pdpotrs)
    1211              :                ! ---------------------------------------------------------------------
    1212           30 :                IF (.NOT. use_dist) THEN
    1213           30 :                   CALL timeset(routineN//"_dpotrf", handle_dpotrf)
    1214           30 :                   CALL dpotrf('L', n_local_grid, D_local, n_local_grid, info)
    1215           30 :                   CALL timestop(handle_dpotrf)
    1216           30 :                   CALL timeset(routineN//"_dpotrs", handle_dpotrs)
    1217              :                   CALL dpotrs('L', n_local_grid, n_loc_ri, D_local, n_local_grid, &
    1218           30 :                               d_lp_local, n_local_grid, info)
    1219           30 :                   CALL timestop(handle_dpotrs)
    1220           30 :                   DEALLOCATE (D_local)
    1221              :                ELSE
    1222              :                   CALL solve_D_lp_distributed(phi_local, d_vec_local, d_lp_local, &
    1223              :                                               n_local_grid, n_ao_total, n_loc_ri, &
    1224              :                                               bs_env%ri_rs%tikhonov, &
    1225              :                                               para_env_sub, blacs_env_sub, &
    1226            0 :                                               fm_struct_D, fm_struct_b, fm_D, fm_b, info)
    1227              :                END IF
    1228              : 
    1229              :                ! ---------------------------------------------------------------------
    1230              :                ! F. Undo the conditioning:  Z_lP = d_l * Z'_lP
    1231              :                ! ---------------------------------------------------------------------
    1232           30 :                CALL scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
    1233              : 
    1234              :                ! ---------------------------------------------------------------------
    1235              :                ! G. Scatter the solved Z columns back into the global sparse mat_Z_lP.
    1236              :                ! ---------------------------------------------------------------------
    1237           30 :                do_scatter = .TRUE.
    1238           30 :                IF (use_dist) do_scatter = (para_env_sub%mepos == 0)
    1239            0 :                IF (do_scatter) THEN
    1240              :                   CALL scatter_z_columns(mat_Z_lP, d_lp_local, local_grid_idx, n_local_grid, &
    1241              :                                          n_loc_ri, atom_P, r_blk_sizes, row_offset, &
    1242           30 :                                          bs_env%eps_filter)
    1243              :                END IF
    1244              : 
    1245           30 :                DEALLOCATE (d_vec_local, d_lp_local)
    1246           30 :                DEALLOCATE (local_grid_idx, phi_local)
    1247              : 
    1248              :                ! Progress based on rank 0 only
    1249           30 :                n_done = n_done + 1
    1250           56 :                IF (bs_env%unit_nr > 0 .AND. n_my_atoms > 0) THEN
    1251           68 :                   DO WHILE (next_pct <= 100 .AND. n_done*100 >= next_pct*n_my_atoms)
    1252           52 :                      WRITE (bs_env%unit_nr, '(T2,A,I57,A)') 'Computing Z_lP:', next_pct, ' % done'
    1253           55 :                      next_pct = next_pct + 25
    1254              :                   END DO
    1255              :                END IF
    1256              :             END DO   ! idx: atoms of this phase owned by this rank / subgroup
    1257              : 
    1258              :             ! Tear down the Phase-B subgroup (all ranks created it collectively).
    1259           52 :             IF (iphase == 2) THEN
    1260            0 :                CALL cp_blacs_env_release(blacs_env_sub)
    1261            0 :                CALL para_env_sub%free()
    1262            0 :                DEALLOCATE (para_env_sub)
    1263              :             END IF
    1264              :          END DO   ! iphase
    1265              : 
    1266           26 :          DEALLOCATE (cutoff_ri_per_atom)
    1267           26 :          DEALLOCATE (small_list, big_list)
    1268              : 
    1269           26 :          CALL gw_3c_ctx_release(ctx_3c)
    1270              : 
    1271           26 :          CALL dbcsr_finalize(mat_Z_lP)
    1272              : 
    1273           26 :          CALL print_matrix_occupation(mat_Z_lP, 'Z(l,P)', para_env, bs_env%unit_nr)
    1274              : 
    1275           26 :          IF (bs_env%unit_nr > 0) THEN
    1276           13 :             WRITE (bs_env%unit_nr, '(A)') ' '
    1277              :             WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
    1278           13 :                'Computed Z_lP ', ' Execution time', m_walltime() - t1, ' s'
    1279           13 :             WRITE (bs_env%unit_nr, '(A)') ' '
    1280              :          END IF
    1281              : 
    1282           26 :          logger => cp_get_default_logger()
    1283              : 
    1284           26 :          IF (BTEST(cp_print_key_should_output(logger%iter_info, input, key), cp_p_file)) THEN
    1285            0 :             CALL dbcsr_binary_write(matrix=mat_Z_lP, filepath=TRIM(bs_env%prefix)//"Z_lP.matrix")
    1286              :          END IF
    1287              : 
    1288              :       END IF
    1289              : 
    1290           28 :       DEALLOCATE (row_offset, ri_blk_sizes, col_dist_ri)
    1291           28 :       CALL dbcsr_distribution_release(dist_Z)
    1292              : 
    1293           28 :       DEALLOCATE (ri_rs_grid_points)
    1294              : 
    1295           28 :       CALL timestop(handle)
    1296              : 
    1297          140 :    END SUBROUTINE compute_coeff_Z_lP
    1298              : 
    1299              : ! **************************************************************************************************
    1300              : !> \brief Prints the per-kind maximum RI-RS integration-sphere cutoff table.
    1301              : !> \param bs_env ...
    1302              : !> \param atomic_kind_set ...
    1303              : !> \param particle_set ...
    1304              : !> \param cutoff_ri_per_atom ...
    1305              : ! **************************************************************************************************
    1306           26 :    SUBROUTINE print_sphere_cutoff_table(bs_env, atomic_kind_set, particle_set, cutoff_ri_per_atom)
    1307              : 
    1308              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1309              :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
    1310              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1311              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: cutoff_ri_per_atom
    1312              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'print_sphere_cutoff_table'
    1313              : 
    1314              :       INTEGER                                            :: handle
    1315              : 
    1316              :       INTEGER                                            :: iatom, ikind, nkind
    1317           26 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cutoff_ri_per_kind
    1318              : 
    1319           26 :       CALL timeset(routineN, handle)
    1320              : 
    1321           26 :       IF (bs_env%unit_nr <= 0) THEN
    1322           13 :          CALL timestop(handle)
    1323              :          RETURN
    1324              :       END IF
    1325              : 
    1326           13 :       nkind = SIZE(atomic_kind_set)
    1327           39 :       ALLOCATE (cutoff_ri_per_kind(nkind))
    1328           13 :       cutoff_ri_per_kind(:) = 0.0_dp
    1329              : 
    1330           43 :       DO iatom = 1, SIZE(particle_set)
    1331           30 :          ikind = particle_set(iatom)%atomic_kind%kind_number
    1332           43 :          cutoff_ri_per_kind(ikind) = MAX(cutoff_ri_per_kind(ikind), cutoff_ri_per_atom(iatom))
    1333              :       END DO
    1334              : 
    1335           13 :       WRITE (bs_env%unit_nr, '(T2,A)') 'Per-kind maximum RI-RS sphere cutoff (Å):'
    1336           13 :       WRITE (bs_env%unit_nr, '(T4,A4,A14)') 'Kind', 'cutoff (Å)'
    1337           30 :       DO ikind = 1, nkind
    1338              :          WRITE (bs_env%unit_nr, '(T4,A4,F14.4)') &
    1339           17 :             atomic_kind_set(ikind)%element_symbol, &
    1340           47 :             cutoff_ri_per_kind(ikind)*angstrom
    1341              :       END DO
    1342           13 :       WRITE (bs_env%unit_nr, '(A)') ' '
    1343              : 
    1344           13 :       DEALLOCATE (cutoff_ri_per_kind)
    1345              : 
    1346           13 :       CALL timestop(handle)
    1347              : 
    1348           26 :    END SUBROUTINE print_sphere_cutoff_table
    1349              : 
    1350              : ! **************************************************************************************************
    1351              : !> \brief Splits the atoms of the Z_lP solve into a single-rank list ("small", Phase A: LAPACK
    1352              : !>        dpotrf/dpotrs on one rank) and a distributed list ("big", Phase B: ScaLAPACK
    1353              : !>        pdpotrf/pdpotrs over a rank subgroup of size G), and sizes G.
    1354              : !>        AUTO mode (N_PROCS_PER_ATOM_Z_LP <= 0, the default): estimate each atom's single-rank
    1355              : !>        peak memory
    1356              : !>          peak(P) = 8*n_local_grid(P)^2                (dense Gram matrix D_local)
    1357              : !>                  + 8*n_local_grid(P)*n_ao             (phi_local)
    1358              : !>                  + 8*n_local_grid(P)*n_RI(P)*(1+n_threads)   (d_lp + OMP partials)
    1359              : !>        and send atoms whose peak exceeds mem_safety * available-memory-per-proc to the
    1360              : !>        distributed path; G is auto-sized so the biggest atom's distributed D_local (/G)
    1361              : !>        fits alongside the replicated phi_local + d_lp.
    1362              : !>        MANUAL mode (> 0): 1 forces the single-rank path for every atom; > 1 keeps the
    1363              : !>        memory-based classification but forces that fixed subgroup size G.
    1364              : !>        In every mode G is floored by the ScaLAPACK 32-bit index limit (a local block-cyclic
    1365              : !>        slice of ~n_local_grid^2/G elements must stay below 2^31 or pdpotrf segfaults).
    1366              : !> \param bs_env ...
    1367              : !> \param para_env ...
    1368              : !> \param ri_rs_grid_points ...
    1369              : !> \param particle_set ...
    1370              : !> \param cutoff_ri_per_atom ...
    1371              : !> \param ri_blk_sizes per-atom ...
    1372              : !> \param n_ao_total ...
    1373              : !> \param n_local_grid_atom ...
    1374              : !> \param small_list ...
    1375              : !> \param n_small ...
    1376              : !> \param big_list ...
    1377              : !> \param n_big ...
    1378              : !> \param G ...
    1379              : ! **************************************************************************************************
    1380           26 :    SUBROUTINE classify_z_lp_atoms(bs_env, para_env, ri_rs_grid_points, particle_set, &
    1381           26 :                                   cutoff_ri_per_atom, ri_blk_sizes, n_ao_total, &
    1382              :                                   n_local_grid_atom, small_list, n_small, big_list, n_big, G)
    1383              : 
    1384              : !$    USE OMP_LIB, ONLY: omp_get_max_threads
    1385              : 
    1386              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1387              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    1388              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    1389              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1390              : 
    1391              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: cutoff_ri_per_atom
    1392              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ri_blk_sizes
    1393              :       INTEGER, INTENT(IN)                                :: n_ao_total
    1394              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: n_local_grid_atom, small_list, big_list
    1395              :       INTEGER, INTENT(OUT)                               :: n_small, n_big, G
    1396              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'classify_z_lp_atoms'
    1397              : 
    1398              :       INTEGER                                            :: handle
    1399              : 
    1400              :       ! Conservative fraction of measured available memory usable per rank for the Z_lP
    1401              :       REAL(KIND=dp), PARAMETER                           :: mem_safety = 0.8_dp
    1402              : 
    1403              :       ! ScaLAPACK/BLACS index the per-rank local block-cyclic slice (~n_local_grid^2/G
    1404              :       ! elements) with 32-bit integers; keep it safely below 2^31 or pdpotrf segfaults.
    1405              :       REAL(KIND=dp), PARAMETER                           :: scalapack_loc_limit = 2.0E9_dp
    1406              : 
    1407              :       INTEGER                                            :: G_atom, G_int32, G_int32_max, l, &
    1408              :                                                             n_grid_total, n_local_grid, natom, &
    1409              :                                                             nthreads_cls, P_loop_atom
    1410              :       LOGICAL                                            :: auto_mode
    1411              :       REAL(KIND=dp)                                      :: budget_bytes, cutoff_ri, dlp_bytes, &
    1412              :                                                             mem_avail_GB, ng, nri, peak_bytes, &
    1413              :                                                             phi_bytes
    1414              :       REAL(KIND=dp), DIMENSION(3)                        :: pos_P
    1415              : 
    1416           26 :       CALL timeset(routineN, handle)
    1417              : 
    1418           26 :       natom = SIZE(particle_set)
    1419           26 :       n_grid_total = SIZE(ri_rs_grid_points, 2)
    1420              : 
    1421              :       ! Per-atom sphere size: n_local_grid(P) = #{ l : |r_l - R_P| <= cutoff_ri(P) }.
    1422              :       ! It sets both the memory footprint (D_local is n_local_grid^2) and the solve
    1423              :       ! cost (~n_local_grid^3), so it drives classification and the LPT load balancing.
    1424          130 :       ALLOCATE (n_local_grid_atom(natom), small_list(natom), big_list(natom))
    1425           86 :       DO P_loop_atom = 1, natom
    1426          240 :          pos_P(:) = particle_set(P_loop_atom)%r(:)
    1427           60 :          cutoff_ri = cutoff_ri_per_atom(P_loop_atom)
    1428           60 :          n_local_grid = 0
    1429        26540 :          DO l = 1, n_grid_total
    1430       105980 :             IF (SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2) <= cutoff_ri**2) then
    1431        25094 :                n_local_grid = n_local_grid + 1
    1432              :             end if
    1433              :          END DO
    1434           86 :          n_local_grid_atom(P_loop_atom) = n_local_grid
    1435              :       END DO
    1436              : 
    1437           26 :       nthreads_cls = 1
    1438           26 : !$    nthreads_cls = omp_get_max_threads()
    1439              :       ! N_PROCS_PER_ATOM_Z_LP: -1 (default) = AUTO (classify by memory, auto-size G);
    1440              :       ! 1 = force single-rank BLAS for every atom; >1 = classify by memory but use this
    1441              :       ! fixed subgroup size G for the big atoms.
    1442           26 :       auto_mode = (bs_env%ri_rs%n_procs_per_atom_z_lp <= 0)
    1443           26 :       CALL ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)  ! collective over all ranks
    1444           26 :       budget_bytes = mem_safety*mem_avail_GB*1.0E9_dp
    1445              : 
    1446           26 :       n_small = 0
    1447           26 :       n_big = 0
    1448           26 :       G = 1
    1449           26 :       G_atom = 1              ! max G a big atom needs (memory + ScaLAPACK int32 floor)
    1450           26 :       G_int32_max = 1         ! max ScaLAPACK-int32 floor over the distributed atoms
    1451           26 :       IF (bs_env%ri_rs%n_procs_per_atom_z_lp == 1) THEN
    1452              :          ! Force single-rank BLAS for every atom.
    1453            0 :          DO P_loop_atom = 1, natom
    1454            0 :             n_small = n_small + 1
    1455            0 :             small_list(n_small) = P_loop_atom
    1456              :          END DO
    1457           26 :       ELSE IF (mem_avail_GB <= 0.0_dp) THEN
    1458              :          ! No /proc/meminfo => cannot size by memory.
    1459            0 :          IF (auto_mode) THEN
    1460            0 :             IF (bs_env%unit_nr > 0) then
    1461            0 :                CPWARN("RI-RS Z_lP: no meminfo; single-rank solve for all atoms")
    1462              :             end if
    1463            0 :             DO P_loop_atom = 1, natom
    1464            0 :                n_small = n_small + 1
    1465            0 :                small_list(n_small) = P_loop_atom
    1466              :             END DO
    1467              :          ELSE
    1468              :             ! Fixed G, no meminfo: distribute all atoms; still floor G by the int32 limit.
    1469            0 :             DO P_loop_atom = 1, natom
    1470            0 :                ng = REAL(n_local_grid_atom(P_loop_atom), dp)
    1471            0 :                G_int32_max = MAX(G_int32_max, CEILING(ng*ng/scalapack_loc_limit))
    1472            0 :                n_big = n_big + 1
    1473            0 :                big_list(n_big) = P_loop_atom
    1474              :             END DO
    1475            0 :             G = MIN(bs_env%ri_rs%n_procs_per_atom_z_lp, para_env%num_pe)
    1476            0 :             IF (G < G_int32_max) THEN
    1477            0 :                G = MIN(G_int32_max, para_env%num_pe)
    1478            0 :                IF (bs_env%unit_nr > 0) then
    1479            0 :                   CPWARN("RI-RS Z_lP: raised G to avoid ScaLAPACK overflow")
    1480              :                end if
    1481              :             END IF
    1482              :          END IF
    1483              :       ELSE
    1484              :          ! Classify by memory: peak (D_local + phi_local + d_lp) vs budget. Small -> BLAS,
    1485              :          ! big -> distributed. Same classification for AUTO and fixed-G modes.
    1486           86 :          DO P_loop_atom = 1, natom
    1487           60 :             ng = REAL(n_local_grid_atom(P_loop_atom), dp)
    1488           60 :             nri = REAL(ri_blk_sizes(P_loop_atom), dp)
    1489           60 :             phi_bytes = 8.0_dp*ng*REAL(n_ao_total, dp)
    1490           60 :             dlp_bytes = 8.0_dp*ng*nri*REAL(1 + nthreads_cls, dp)
    1491           60 :             peak_bytes = 8.0_dp*ng*ng + phi_bytes + dlp_bytes
    1492           86 :             IF (peak_bytes <= budget_bytes) THEN
    1493           60 :                n_small = n_small + 1
    1494           60 :                small_list(n_small) = P_loop_atom
    1495              :             ELSE
    1496            0 :                n_big = n_big + 1
    1497            0 :                big_list(n_big) = P_loop_atom
    1498              :                ! G must satisfy BOTH: (a) memory — distributed D_local (/G) fits next to the
    1499              :                ! replicated phi_local + d_lp; (b) ScaLAPACK — local ~ng^2/G below the int32 limit.
    1500            0 :                G_int32 = CEILING(ng*ng/scalapack_loc_limit)
    1501            0 :                G_int32_max = MAX(G_int32_max, G_int32)
    1502              :                G_atom = MAX(G_atom, G_int32, &
    1503            0 :                             CEILING(8.0_dp*ng*ng/MAX(budget_bytes - phi_bytes - dlp_bytes, 1.0_dp)))
    1504              :             END IF
    1505              :          END DO
    1506           26 :          IF (n_big > 0) THEN
    1507            0 :             IF (auto_mode) THEN
    1508              :                ! Auto-size G from the most demanding big atom.
    1509            0 :                IF (G_atom > para_env%num_pe) THEN
    1510            0 :                   IF (bs_env%unit_nr > 0) THEN
    1511              :                      WRITE (bs_env%unit_nr, '(T2,A)') &
    1512              :                         'RI-RS Z_lP: an atom is too large to fit even distributed over all '// &
    1513              :                         'ranks. Add nodes, use fewer MPI ranks/node, lower CUTOFF_RADIUS_RL_RI, '// &
    1514            0 :                         'or raise EPS_FILTER (more grid screening).'
    1515              :                   END IF
    1516            0 :                   CPABORT("RI-RS Z_lP: atom too large even fully distributed")
    1517              :                END IF
    1518            0 :                G = MIN(MAX(G_atom, 2), para_env%num_pe)
    1519              :             ELSE
    1520              :                ! Fixed G from the keyword. Hard-floor by the ScaLAPACK int32 limit (below it
    1521              :                ! pdpotrf segfaults); warn if it is still below the memory recommendation.
    1522            0 :                G = MIN(bs_env%ri_rs%n_procs_per_atom_z_lp, para_env%num_pe)
    1523            0 :                IF (G < G_int32_max) THEN
    1524            0 :                   G = MIN(G_int32_max, para_env%num_pe)
    1525            0 :                   IF (bs_env%unit_nr > 0) then
    1526            0 :                      CPWARN("RI-RS Z_lP: raised G to avoid ScaLAPACK overflow")
    1527              :                   end if
    1528            0 :                ELSE IF (G < G_atom .AND. bs_env%unit_nr > 0) THEN
    1529            0 :                   CPWARN("RI-RS Z_lP: N_PROCS_PER_ATOM_Z_LP too small for the largest atom")
    1530              :                END IF
    1531              :             END IF
    1532              :          END IF
    1533              :       END IF
    1534              : 
    1535           26 :       CALL timestop(handle)
    1536              : 
    1537           52 :    END SUBROUTINE classify_z_lp_atoms
    1538              : 
    1539              : ! **************************************************************************************************
    1540              : !> \brief Builds the sphere-local AO matrix phi_local(l, μ) = Φ_μ(r_l) for one RI atom P
    1541              : !> \param bs_env ...
    1542              : !> \param particle_set ...
    1543              : !> \param qs_kind_set ...
    1544              : !> \param cell ...
    1545              : !> \param ri_rs_grid_points ...
    1546              : !> \param atom_P ...
    1547              : !> \param cutoff_ri ...
    1548              : !> \param n_ao_total ...
    1549              : !> \param local_grid_idx ...
    1550              : !> \param n_local_grid ...
    1551              : !> \param phi_local ...
    1552              : ! **************************************************************************************************
    1553           30 :    SUBROUTINE build_phi_on_sphere(bs_env, particle_set, qs_kind_set, cell, ri_rs_grid_points, &
    1554              :                                   atom_P, cutoff_ri, n_ao_total, local_grid_idx, n_local_grid, &
    1555              :                                   phi_local)
    1556              : 
    1557              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1558              :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1559              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    1560              :       TYPE(cell_type), POINTER                           :: cell
    1561              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    1562              :       INTEGER, INTENT(IN)                                :: atom_P
    1563              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_ri
    1564              :       INTEGER, INTENT(IN)                                :: n_ao_total
    1565              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: local_grid_idx
    1566              :       INTEGER, INTENT(OUT)                               :: n_local_grid
    1567              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1568              :          INTENT(OUT)                                     :: phi_local
    1569              : 
    1570              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'build_phi_on_sphere'
    1571              : 
    1572              :       INTEGER                                            :: col_end, col_start, handle, j, k, l, &
    1573              :                                                             loc_idx, n_grid_total, n_keep, &
    1574              :                                                             source_atom
    1575              :       REAL(KIND=dp)                                      :: d_sP, dist, r2_threshold
    1576           30 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: w_pt
    1577           30 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: phi_keep, sphere_grid
    1578              :       REAL(KIND=dp), DIMENSION(3)                        :: pos_P
    1579              : 
    1580           30 :       CALL timeset(routineN, handle)
    1581              : 
    1582           30 :       n_grid_total = SIZE(ri_rs_grid_points, 2)
    1583          120 :       pos_P(:) = particle_set(atom_P)%r(:)
    1584              : 
    1585           30 :       n_local_grid = 0
    1586        13270 :       DO l = 1, n_grid_total
    1587        52960 :          dist = SQRT(SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2))
    1588        13270 :          IF (dist <= cutoff_ri) n_local_grid = n_local_grid + 1
    1589              :       END DO
    1590              : 
    1591           90 :       ALLOCATE (local_grid_idx(n_local_grid))
    1592              : 
    1593           30 :       n_local_grid = 0
    1594        13270 :       DO l = 1, n_grid_total
    1595        52960 :          dist = SQRT(SUM((ri_rs_grid_points(1:3, l) - pos_P(1:3))**2))
    1596        13270 :          IF (dist <= cutoff_ri) THEN
    1597        12547 :             n_local_grid = n_local_grid + 1
    1598        12547 :             local_grid_idx(n_local_grid) = l
    1599              :          END IF
    1600              :       END DO
    1601              : 
    1602           90 :       ALLOCATE (sphere_grid(3, n_local_grid))
    1603        12577 :       DO loc_idx = 1, n_local_grid
    1604        50218 :          sphere_grid(:, loc_idx) = ri_rs_grid_points(:, local_grid_idx(loc_idx))
    1605              :       END DO
    1606              : 
    1607          120 :       ALLOCATE (phi_local(n_local_grid, n_ao_total))
    1608           30 :       phi_local = 0.0_dp
    1609              : 
    1610          102 :       DO source_atom = 1, SIZE(particle_set)
    1611          288 :          d_sP = NORM2(particle_set(source_atom)%r(:) - pos_P(:))
    1612           72 :          IF (d_sP > bs_env%ri_rs%radius_ao_per_atom(source_atom) + cutoff_ri) CYCLE
    1613              : 
    1614           72 :          col_start = bs_env%i_ao_start_from_atom(source_atom)
    1615           72 :          col_end = bs_env%i_ao_end_from_atom(source_atom)
    1616              :          ! A positive CUTOFF_RADIUS_RI_AO overrides the per-atom Gaussian radius
    1617              :          ! with a user-defined hard cutoff.
    1618           72 :          IF (bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
    1619            9 :             r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
    1620              :          ELSE
    1621           63 :             r2_threshold = bs_env%ri_rs%radius_ao_per_atom(source_atom)**2
    1622              :          END IF
    1623              : 
    1624              :          CALL fill_phi_for_atom(phi_local(:, col_start:col_end), sphere_grid, &
    1625              :                                 n_local_grid, source_atom, particle_set, qs_kind_set, &
    1626          102 :                                 cell, r2_threshold)
    1627              :       END DO
    1628              : 
    1629           30 :       DEALLOCATE (sphere_grid)
    1630              : 
    1631           30 :       IF (n_local_grid > 0) THEN
    1632           90 :          ALLOCATE (w_pt(n_local_grid))
    1633              :          !$OMP PARALLEL DO DEFAULT(NONE) &
    1634              :          !$OMP SHARED(n_local_grid, n_ao_total, phi_local, w_pt) &
    1635           30 :          !$OMP PRIVATE(l, j) SCHEDULE(STATIC)
    1636              :          DO l = 1, n_local_grid
    1637              :             w_pt(l) = 0.0_dp
    1638              :             DO j = 1, n_ao_total
    1639              :                w_pt(l) = MAX(w_pt(l), ABS(phi_local(l, j)))
    1640              :             END DO
    1641              :          END DO
    1642              :          !$OMP END PARALLEL DO
    1643        12577 :          n_keep = COUNT(w_pt > bs_env%eps_filter)
    1644           30 :          IF (n_keep < n_local_grid) THEN
    1645            0 :             ALLOCATE (phi_keep(n_keep, n_ao_total))
    1646            0 :             k = 0
    1647            0 :             DO l = 1, n_local_grid
    1648            0 :                IF (w_pt(l) > bs_env%eps_filter) THEN
    1649            0 :                   k = k + 1
    1650            0 :                   phi_keep(k, :) = phi_local(l, :)
    1651            0 :                   local_grid_idx(k) = local_grid_idx(l)
    1652              :                END IF
    1653              :             END DO
    1654            0 :             CALL MOVE_ALLOC(phi_keep, phi_local)
    1655            0 :             n_local_grid = n_keep
    1656              :          END IF
    1657           30 :          DEALLOCATE (w_pt)
    1658              :       END IF
    1659              : 
    1660           30 :       CALL timestop(handle)
    1661              : 
    1662           60 :    END SUBROUTINE build_phi_on_sphere
    1663              : 
    1664              : ! **************************************************************************************************
    1665              : !> \brief Builds the dense Jacobi-conditioned Gram matrix and the conditioning vector for the
    1666              : !>        single-rank (BLAS/LAPACK) Z_lP solve:
    1667              : !>          D_ll'   = [ Σ_μ Φ_μ(r_l) Φ_μ(r_l') ]²     (dsyrk of phi_local, then squared)
    1668              : !>          d_l     = 1 / sqrt(D_ll)
    1669              : !>          D'_ll'  = d_l D_ll' d_l' + λ δ_ll'
    1670              : !>        Only the lower triangle is referenced by the subsequent dpotrf('L')
    1671              : !> \param phi_local ...
    1672              : !> \param n_local_grid ...
    1673              : !> \param n_ao_total ...
    1674              : !> \param tikhonov ...
    1675              : !> \param D_local ...
    1676              : !> \param d_vec_local ...
    1677              : ! **************************************************************************************************
    1678           30 :    SUBROUTINE build_gram_jacobi_blas(phi_local, n_local_grid, n_ao_total, tikhonov, D_local, &
    1679           30 :                                      d_vec_local)
    1680              : 
    1681              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_local
    1682              :       INTEGER, INTENT(IN)                                :: n_local_grid, n_ao_total
    1683              :       REAL(KIND=dp), INTENT(IN)                          :: tikhonov
    1684              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1685              :          INTENT(OUT)                                     :: D_local
    1686              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: d_vec_local
    1687              : 
    1688              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'build_gram_jacobi_blas'
    1689              : 
    1690              :       INTEGER                                            :: handle, handle_dsyrk, i, j
    1691              : 
    1692           30 :       CALL timeset(routineN, handle)
    1693              : 
    1694          120 :       ALLOCATE (D_local(n_local_grid, n_local_grid))
    1695           30 :       D_local = 0.0_dp
    1696              : 
    1697              :       ! D_ll' = Σ_μ Φ_μ(r_l) Φ_μ(r_l')   (lower triangle only)
    1698           30 :       CALL timeset(routineN//"_dsyrk", handle_dsyrk)
    1699              :       CALL dsyrk("L", "N", n_local_grid, n_ao_total, 1.0_dp, phi_local, &
    1700           30 :                  n_local_grid, 0.0_dp, D_local, n_local_grid)
    1701           30 :       CALL timestop(handle_dsyrk)
    1702              : 
    1703              :       ! Diagonal: square, derive d_l = 1/sqrt(D_ll), scale, add Tikhonov λ
    1704              :       !$OMP PARALLEL DO DEFAULT(NONE) &
    1705              :       !$OMP SHARED(n_local_grid, D_local, d_vec_local, tikhonov) &
    1706              :       !$OMP PRIVATE(i) &
    1707           30 :       !$OMP SCHEDULE(STATIC)
    1708              :       DO i = 1, n_local_grid
    1709              :          D_local(i, i) = D_local(i, i)**2
    1710              :          d_vec_local(i) = 1.0_dp/SQRT(MAX(D_local(i, i), 1.0E-16_dp))
    1711              :          D_local(i, i) = (D_local(i, i)*d_vec_local(i)**2) + tikhonov
    1712              :       END DO
    1713              :       !$OMP END PARALLEL DO
    1714              : 
    1715              :       ! Off-diagonal: D'_ll' = d_l D_ll'^2 d_l' (mirror to the upper triangle)
    1716              :       !$OMP PARALLEL DO DEFAULT(NONE) &
    1717              :       !$OMP SHARED(n_local_grid, D_local, d_vec_local) &
    1718              :       !$OMP PRIVATE(j, i) &
    1719           30 :       !$OMP SCHEDULE(DYNAMIC)
    1720              :       DO j = 1, n_local_grid
    1721              :          DO i = j + 1, n_local_grid
    1722              :             D_local(i, j) = D_local(i, j)**2
    1723              :             D_local(i, j) = D_local(i, j)*d_vec_local(i)*d_vec_local(j)
    1724              :             D_local(j, i) = D_local(i, j)
    1725              :          END DO
    1726              :       END DO
    1727              :       !$OMP END PARALLEL DO
    1728              : 
    1729           30 :       CALL timestop(handle)
    1730              : 
    1731           60 :    END SUBROUTINE build_gram_jacobi_blas
    1732              : 
    1733              : ! **************************************************************************************************
    1734              : !> \brief Computes the Jacobi conditioning vector directly from phi for the distributed
    1735              : !>        (ScaLAPACK) Z_lP solve:  d_l = 1 / Σ_μ Φ_μ(r_l)² = 1/sqrt(D_ll), without forming
    1736              : !>        the Gram matrix.
    1737              : !> \param phi_local ...
    1738              : !> \param n_local_grid ...
    1739              : !> \param n_ao_total ...
    1740              : !> \param d_vec_local ...
    1741              : ! **************************************************************************************************
    1742            0 :    SUBROUTINE build_jacobi_diag_from_phi(phi_local, n_local_grid, n_ao_total, d_vec_local)
    1743              : 
    1744              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_local
    1745              :       INTEGER, INTENT(IN)                                :: n_local_grid, n_ao_total
    1746              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: d_vec_local
    1747              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'build_jacobi_diag_from_phi'
    1748              : 
    1749              :       INTEGER                                            :: handle
    1750              : 
    1751              :       INTEGER                                            :: i, j
    1752              : 
    1753            0 :       CALL timeset(routineN, handle)
    1754              : 
    1755              :       !$OMP PARALLEL DO DEFAULT(NONE) &
    1756              :       !$OMP SHARED(n_local_grid, n_ao_total, phi_local, d_vec_local) &
    1757              :       !$OMP PRIVATE(i, j) &
    1758            0 :       !$OMP SCHEDULE(STATIC)
    1759              :       DO i = 1, n_local_grid
    1760              :          d_vec_local(i) = 0.0_dp
    1761              :          DO j = 1, n_ao_total
    1762              :             d_vec_local(i) = d_vec_local(i) + phi_local(i, j)*phi_local(i, j)
    1763              :          END DO
    1764              :          d_vec_local(i) = 1.0_dp/MAX(d_vec_local(i), 1.0E-16_dp)
    1765              :       END DO
    1766              :       !$OMP END PARALLEL DO
    1767              : 
    1768            0 :       CALL timestop(handle)
    1769              : 
    1770            0 :    END SUBROUTINE build_jacobi_diag_from_phi
    1771              : 
    1772              : ! **************************************************************************************************
    1773              : !> \brief Scales every row of a matrix by the corresponding diagonal entry,
    1774              : !>        A(l, :) <- d_l * A(l, :). Used in Z_lP solve: to pre-scale the RHS
    1775              : !>        (D'_lP = d_l D_lP) and to undo the conditioning of the solution (Z_lP = d_l Z'_lP).
    1776              : !> \param d_lp_local ...
    1777              : !> \param d_vec_local ...
    1778              : !> \param n_local_grid ...
    1779              : !> \param n_loc_ri ...
    1780              : ! **************************************************************************************************
    1781           60 :    SUBROUTINE scale_rows_by_diag(d_lp_local, d_vec_local, n_local_grid, n_loc_ri)
    1782              : 
    1783              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp_local
    1784              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: d_vec_local
    1785              :       INTEGER, INTENT(IN)                                :: n_local_grid, n_loc_ri
    1786              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'scale_rows_by_diag'
    1787              : 
    1788              :       INTEGER                                            :: handle
    1789              : 
    1790              :       INTEGER                                            :: i, j_ri
    1791              : 
    1792           60 :       CALL timeset(routineN, handle)
    1793              : 
    1794              :       !$OMP PARALLEL DO DEFAULT(NONE) &
    1795              :       !$OMP SHARED(n_loc_ri, n_local_grid, d_lp_local, d_vec_local) &
    1796              :       !$OMP PRIVATE(j_ri, i) &
    1797           60 :       !$OMP SCHEDULE(STATIC)
    1798              :       DO j_ri = 1, n_loc_ri
    1799              :          DO i = 1, n_local_grid
    1800              :             d_lp_local(i, j_ri) = d_lp_local(i, j_ri)*d_vec_local(i)
    1801              :          END DO
    1802              :       END DO
    1803              :       !$OMP END PARALLEL DO
    1804              : 
    1805           60 :       CALL timestop(handle)
    1806              : 
    1807           60 :    END SUBROUTINE scale_rows_by_diag
    1808              : 
    1809              : ! **************************************************************************************************
    1810              : !> \brief Scatters the solved Z columns of one atom P from the dense sphere-local solution back
    1811              : !>        into the global sparse mat_Z_lP
    1812              : !> \param mat_Z_lP ...
    1813              : !> \param d_lp_local ...
    1814              : !> \param local_grid_idx ...
    1815              : !> \param n_local_grid ...
    1816              : !> \param n_loc_ri ...
    1817              : !> \param atom_P ...
    1818              : !> \param r_blk_sizes ...
    1819              : !> \param row_offset ...
    1820              : !> \param eps_filter ...
    1821              : ! **************************************************************************************************
    1822           30 :    SUBROUTINE scatter_z_columns(mat_Z_lP, d_lp_local, local_grid_idx, n_local_grid, n_loc_ri, &
    1823           30 :                                 atom_P, r_blk_sizes, row_offset, eps_filter)
    1824              : 
    1825              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_Z_lP
    1826              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: d_lp_local
    1827              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: local_grid_idx
    1828              :       INTEGER, INTENT(IN)                                :: n_local_grid, n_loc_ri, atom_P
    1829              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes, row_offset
    1830              :       REAL(KIND=dp), INTENT(IN)                          :: eps_filter
    1831              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'scatter_z_columns'
    1832              :       INTEGER                                            :: handle
    1833              : 
    1834              :       INTEGER                                            :: current_chunk_size, g_pt, i_blk, &
    1835              :                                                             loc_ptr, r_end, r_start
    1836           30 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: Z_blk
    1837              : 
    1838           30 :       CALL timeset(routineN, handle)
    1839              : 
    1840          584 :       ALLOCATE (Z_blk(MAXVAL(r_blk_sizes), n_loc_ri))
    1841           30 :       loc_ptr = 1
    1842              : 
    1843          494 :       DO i_blk = 1, SIZE(r_blk_sizes)
    1844          464 :          r_start = row_offset(i_blk) + 1
    1845          464 :          r_end = row_offset(i_blk) + r_blk_sizes(i_blk)
    1846          464 :          current_chunk_size = r_blk_sizes(i_blk)
    1847              : 
    1848          464 :          Z_blk = 0.0_dp
    1849              : 
    1850              :          ! Copy the sphere points whose global grid index falls inside this block
    1851        13011 :          DO WHILE (loc_ptr <= n_local_grid)
    1852        12977 :             g_pt = local_grid_idx(loc_ptr)
    1853        12977 :             IF (g_pt > r_end) EXIT
    1854       179550 :             Z_blk(g_pt - r_start + 1, 1:n_loc_ri) = d_lp_local(loc_ptr, 1:n_loc_ri)
    1855        12977 :             loc_ptr = loc_ptr + 1
    1856              :          END DO
    1857              : 
    1858       178262 :          IF (MAXVAL(ABS(Z_blk(1:current_chunk_size, 1:n_loc_ri))) > eps_filter) THEN
    1859              :             CALL dbcsr_put_block(mat_Z_lP, row=i_blk, col=atom_P, &
    1860          452 :                                  block=Z_blk(1:current_chunk_size, 1:n_loc_ri))
    1861              :          END IF
    1862              :       END DO
    1863              : 
    1864           30 :       DEALLOCATE (Z_blk)
    1865              : 
    1866           30 :       CALL timestop(handle)
    1867              : 
    1868           30 :    END SUBROUTINE scatter_z_columns
    1869              : 
    1870              : ! **************************************************************************************************
    1871              : !> \brief LPT (longest-processing-time) assignment of the Z_lP atoms to workers (MPI ranks in
    1872              : !>        Phase A, rank subgroups in Phase B): sort by estimated solve cost n_local_grid^3
    1873              : !>        (the per-atom Cholesky dominates; the n^2 assembly terms order the atoms the same
    1874              : !>        way) and greedily give each atom to the least-loaded worker.
    1875              : !> \param atom_list ...
    1876              : !> \param n_atoms ...
    1877              : !> \param n_local_grid_atom ...
    1878              : !> \param n_workers ...
    1879              : !> \param my_worker ...
    1880              : !> \param my_atoms ...
    1881              : !> \param max_over_mean ...
    1882              : ! **************************************************************************************************
    1883           26 :    SUBROUTINE lpt_assign_atoms(atom_list, n_atoms, n_local_grid_atom, n_workers, my_worker, &
    1884              :                                my_atoms, max_over_mean)
    1885              : 
    1886              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: atom_list
    1887              :       INTEGER, INTENT(IN)                                :: n_atoms
    1888              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: n_local_grid_atom
    1889              :       INTEGER, INTENT(IN)                                :: n_workers, my_worker
    1890              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: my_atoms
    1891              :       REAL(KIND=dp), INTENT(OUT)                         :: max_over_mean
    1892              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'lpt_assign_atoms'
    1893              :       INTEGER                                            :: handle
    1894              : 
    1895              :       INTEGER                                            :: i, iw, n_mine, w_min
    1896           26 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: mine_tmp, perm
    1897           26 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cost, load
    1898              : 
    1899           26 :       CALL timeset(routineN, handle)
    1900              : 
    1901           26 :       max_over_mean = 1.0_dp
    1902           26 :       IF (n_atoms <= 0) THEN
    1903            0 :          ALLOCATE (my_atoms(0))
    1904            0 :          CALL timestop(handle)
    1905            0 :          RETURN
    1906              :       END IF
    1907              : 
    1908          208 :       ALLOCATE (cost(n_atoms), perm(n_atoms), mine_tmp(n_atoms), load(n_workers))
    1909           86 :       DO i = 1, n_atoms
    1910           86 :          cost(i) = REAL(n_local_grid_atom(atom_list(i)), dp)**3
    1911              :       END DO
    1912           26 :       CALL sort(cost, n_atoms, perm)   ! ascending; walk backwards for largest-first
    1913              : 
    1914           26 :       load(:) = 0.0_dp
    1915           26 :       n_mine = 0
    1916           86 :       DO i = n_atoms, 1, -1
    1917              :          w_min = 1
    1918          120 :          DO iw = 2, n_workers
    1919          120 :             IF (load(iw) < load(w_min)) w_min = iw
    1920              :          END DO
    1921           60 :          load(w_min) = load(w_min) + cost(i)
    1922           86 :          IF (w_min - 1 == my_worker) THEN
    1923           30 :             n_mine = n_mine + 1
    1924           30 :             mine_tmp(n_mine) = atom_list(perm(i))
    1925              :          END IF
    1926              :       END DO
    1927              : 
    1928           78 :       ALLOCATE (my_atoms(n_mine))
    1929           56 :       my_atoms(:) = mine_tmp(1:n_mine)
    1930          182 :       IF (SUM(load) > 0.0_dp) max_over_mean = MAXVAL(load)*REAL(n_workers, dp)/SUM(load)
    1931              : 
    1932           26 :       CALL timestop(handle)
    1933              : 
    1934           52 :    END SUBROUTINE lpt_assign_atoms
    1935              : 
    1936              : ! **************************************************************************************************
    1937              : !> \brief Computes the dense localized RHS d_lp(l,P) = Σ_{μν} Φ_μ(r_l)·Φ_ν(r_l)·(μν|P) for one
    1938              : !>        RI atom P, OMP-threaded over (atom_j, atom_k) AO-pair blocks: per thread, build the 3c
    1939              : !>        block, then contract grid-chunked pair densities into a private d_lp partial; partials
    1940              : !>        are reduced into d_lp at the end.
    1941              : !>        Pair screening is handled inside build_3c_integral_block_ctx via the `screened` output.
    1942              : !> \param bs_env ...
    1943              : !> \param ctx ...
    1944              : !> \param phi_val ...
    1945              : !> \param d_lp ...
    1946              : !> \param n_grid_total ...
    1947              : !> \param n_loc_ri ...
    1948              : !> \param atom_P ...
    1949              : !> \param max_ao_size ...
    1950              : !> \param atom_j_mepos ...
    1951              : !> \param atom_j_stride ...
    1952              : ! **************************************************************************************************
    1953           30 :    SUBROUTINE compute_d_lp(bs_env, ctx, phi_val, d_lp, n_grid_total, n_loc_ri, atom_P, &
    1954              :                            max_ao_size, atom_j_mepos, atom_j_stride)
    1955              : 
    1956              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1957              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1958              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_val
    1959              :       INTEGER, INTENT(IN)                                :: n_grid_total, n_loc_ri
    1960              :       REAL(KIND=dp), INTENT(INOUT)                       :: d_lp(n_grid_total, n_loc_ri)
    1961              :       INTEGER, INTENT(IN)                                :: atom_P, max_ao_size, atom_j_mepos, &
    1962              :                                                             atom_j_stride
    1963              : 
    1964              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_d_lp'
    1965              :       INTEGER, PARAMETER                                 :: grid_chunk = 1024
    1966              : 
    1967              :       INTEGER                                            :: atom_j, atom_k, c, handle, &
    1968              :                                                             handle_dgemm, j, jk_idx, &
    1969              :                                                             jsize, jstart, k, ksize, kstart, l, &
    1970              :                                                             l0, ri
    1971              :       LOGICAL                                            :: screened
    1972           30 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_prv, int_2d_prv, rho_chunk
    1973           30 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: int_3c_prv
    1974           30 :       TYPE(gw_3c_ws_type)                                :: ws
    1975              : 
    1976           30 :       CALL timeset(routineN, handle)
    1977              : 
    1978              :       !$OMP PARALLEL DEFAULT(NONE) &
    1979              :       !$OMP SHARED(bs_env, ctx, phi_val, d_lp, n_grid_total, n_loc_ri, atom_P, max_ao_size, &
    1980              :       !$OMP        atom_j_mepos, atom_j_stride) &
    1981              :       !$OMP PRIVATE(atom_j, atom_k, c, handle_dgemm, j, jk_idx, jsize, jstart, k, ksize, kstart, &
    1982           30 :       !$OMP         l, l0, ri, screened, d_lp_prv, int_2d_prv, rho_chunk, int_3c_prv, ws)
    1983              : 
    1984              :       CALL gw_3c_ws_create(ws, ctx)
    1985              :       ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, n_loc_ri))
    1986              :       ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, n_loc_ri))
    1987              :       ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
    1988              :       ALLOCATE (d_lp_prv(n_grid_total, n_loc_ri))
    1989              :       d_lp_prv(:, :) = 0.0_dp
    1990              : 
    1991              :       ! MPI-stride atom_j over the subgroup (atom_j_stride = 1 for the BLAS
    1992              :       ! path, > 1 for the ScaLAPACK path). The OMP DO parallelizes the inner
    1993              :       ! atom_k while the outer atom_j carries the MPI stride.
    1994              :       !$OMP DO SCHEDULE(DYNAMIC)
    1995              :       DO atom_j = atom_j_mepos + 1, SIZE(bs_env%i_ao_start_from_atom), atom_j_stride
    1996              :          DO atom_k = 1, SIZE(bs_env%i_ao_start_from_atom)
    1997              :             jstart = bs_env%i_ao_start_from_atom(atom_j)
    1998              :             jsize = bs_env%i_ao_end_from_atom(atom_j) - jstart + 1
    1999              :             kstart = bs_env%i_ao_start_from_atom(atom_k)
    2000              :             ksize = bs_env%i_ao_end_from_atom(atom_k) - kstart + 1
    2001              : 
    2002              :             int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri) = 0.0_dp
    2003              : 
    2004              :             ! Compute B_{μν,P} = (μν|P); ctx-internal triangle-inequality screening on
    2005              :             ! kind_radius sets `screened=.TRUE.` for negligible triples.
    2006              :             CALL build_3c_integral_block_ctx(int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri), &
    2007              :                                              ctx, ws, atom_j=atom_j, atom_k=atom_k, atom_i=atom_P, &
    2008              :                                              screened=screened)
    2009              : 
    2010              :             IF (screened) CYCLE
    2011              : 
    2012              :             ! Flatten 3D B_{μν, P} tensor to 2D B_{(μν), P} matrix for BLAS
    2013              :             DO ri = 1, n_loc_ri
    2014              :                DO k = 1, ksize
    2015              :                   DO j = 1, jsize
    2016              :                      jk_idx = (k - 1)*jsize + j
    2017              :                      int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
    2018              :                   END DO
    2019              :                END DO
    2020              :             END DO
    2021              : 
    2022              :             ! Pair density ρ(l, μν) = Φ_μ(r_l) Φ_ν(r_l) in grid chunks, contracted on the fly:
    2023              :             ! d_{l,P} += ρ(l, μν) B_{(μν),P}  (dgemm runs serially inside the parallel region)
    2024              :             DO l0 = 1, n_grid_total, grid_chunk
    2025              :                c = MIN(grid_chunk, n_grid_total - l0 + 1)
    2026              :                DO k = 1, ksize
    2027              :                   DO j = 1, jsize
    2028              :                      jk_idx = (k - 1)*jsize + j
    2029              :                      DO l = 1, c
    2030              :                         rho_chunk(l, jk_idx) = phi_val(l0 + l - 1, jstart + j - 1)* &
    2031              :                                                phi_val(l0 + l - 1, kstart + k - 1)
    2032              :                      END DO
    2033              :                   END DO
    2034              :                END DO
    2035              :                CALL timeset(routineN//"_dgemm", handle_dgemm)
    2036              :                CALL dgemm("N", "N", c, n_loc_ri, jsize*ksize, &
    2037              :                           1.0_dp, rho_chunk, grid_chunk, &
    2038              :                           int_2d_prv, max_ao_size*max_ao_size, &
    2039              :                           1.0_dp, d_lp_prv(l0, 1), n_grid_total)
    2040              :                CALL timestop(handle_dgemm)
    2041              :             END DO
    2042              :          END DO
    2043              :       END DO
    2044              :       !$OMP END DO
    2045              : 
    2046              :       !$OMP CRITICAL (compute_d_lp_reduce)
    2047              :       d_lp(1:n_grid_total, 1:n_loc_ri) = d_lp(1:n_grid_total, 1:n_loc_ri) + &
    2048              :                                          d_lp_prv(1:n_grid_total, 1:n_loc_ri)
    2049              :       !$OMP END CRITICAL (compute_d_lp_reduce)
    2050              : 
    2051              :       DEALLOCATE (int_3c_prv, int_2d_prv, rho_chunk, d_lp_prv)
    2052              :       CALL gw_3c_ws_release(ws)
    2053              : 
    2054              :       !$OMP END PARALLEL
    2055              : 
    2056           30 :       CALL timestop(handle)
    2057              : 
    2058           30 :    END SUBROUTINE compute_d_lp
    2059              : 
    2060              : ! **************************************************************************************************
    2061              : !> \brief Distributed pdpotrf/pdpotrs solve of D x = b for one atom of the
    2062              : !>        RI-RS Z_lP build (Phase B, "big" atoms), called with a subgroup of
    2063              : !>        cooperating ranks and an associated BLACS context.
    2064              : !>        Each rank in the subgroup holds the (replicated) phi_local and the
    2065              : !>        (replicated) RHS d_lp; it builds its own block-cyclic slice of the
    2066              : !>        squared+Jacobi-scaled Gram matrix D via tiled DGEMM, factorizes via
    2067              : !>        cp_fm_cholesky_decompose (UPLO='U'), and solves with
    2068              : !>        cp_fm_cholesky_solve. The replicated d_lp is updated in place via
    2069              : !>        cp_fm_get_submatrix.
    2070              : !> \param phi_local ...
    2071              : !> \param d_vec ...
    2072              : !> \param d_lp ...
    2073              : !> \param n_loc ...
    2074              : !> \param n_ao ...
    2075              : !> \param n_rhs ...
    2076              : !> \param tikhonov ...
    2077              : !> \param para_env_sub ...
    2078              : !> \param blacs_env_sub ...
    2079              : !> \param fm_struct_D ...
    2080              : !> \param fm_struct_b ...
    2081              : !> \param fm_D ...
    2082              : !> \param fm_b ...
    2083              : !> \param info ...
    2084              : ! **************************************************************************************************
    2085            0 :    SUBROUTINE solve_D_lp_distributed(phi_local, d_vec, d_lp, n_loc, n_ao, n_rhs, &
    2086              :                                      tikhonov, para_env_sub, blacs_env_sub, &
    2087              :                                      fm_struct_D, fm_struct_b, fm_D, fm_b, info)
    2088              : 
    2089              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_local
    2090              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: d_vec
    2091              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp
    2092              :       INTEGER, INTENT(IN)                                :: n_loc, n_ao, n_rhs
    2093              :       REAL(KIND=dp), INTENT(IN)                          :: tikhonov
    2094              :       TYPE(mp_para_env_type), POINTER                    :: para_env_sub
    2095              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env_sub
    2096              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_D, fm_struct_b
    2097              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_D, fm_b
    2098              :       INTEGER, INTENT(OUT)                               :: info
    2099              : 
    2100              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'solve_D_lp_distributed'
    2101              : 
    2102              :       INTEGER                                            :: handle, i_loc, ig, j_loc, jg, &
    2103              :                                                             ncol_local, nrow_local
    2104            0 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
    2105              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    2106            0 :          POINTER                                         :: local_data
    2107              : 
    2108            0 :       CALL timeset(routineN, handle)
    2109            0 :       info = 0
    2110              : 
    2111            0 :       NULLIFY (fm_struct_D, fm_struct_b)
    2112              :       CALL cp_fm_struct_create(fm_struct_D, para_env=para_env_sub, &
    2113              :                                context=blacs_env_sub, &
    2114            0 :                                nrow_global=n_loc, ncol_global=n_loc)
    2115              :       CALL cp_fm_struct_create(fm_struct_b, para_env=para_env_sub, &
    2116              :                                context=blacs_env_sub, &
    2117            0 :                                nrow_global=n_loc, ncol_global=n_rhs)
    2118            0 :       CALL cp_fm_create(fm_D, fm_struct_D)
    2119            0 :       CALL cp_fm_create(fm_b, fm_struct_b)
    2120              : 
    2121              :       ! ---- Build the local block-cyclic slice of fm_D --------------------
    2122              :       ! Tiled DGEMM build: for each (row_tile x col_tile) sub-block we
    2123              :       ! gather small phi strips, DGEMM into a small gram tile, then write
    2124              :       ! the squared & d_vec-scaled result directly into fm_D%local_data.
    2125              :       ! This avoids materialising the full (nrow_local x n_ao),
    2126              :       ! (n_ao x ncol_local), and (nrow_local x ncol_local) buffers.
    2127              :       CALL cp_fm_get_info(fm_D, nrow_local=nrow_local, ncol_local=ncol_local, &
    2128              :                           row_indices=row_indices, col_indices=col_indices, &
    2129            0 :                           local_data=local_data)
    2130              : 
    2131            0 :       IF (nrow_local > 0 .AND. ncol_local > 0) THEN
    2132            0 :          BLOCK
    2133              :             INTEGER, PARAMETER :: ntile = 1024
    2134              :             INTEGER :: ib, ie, jb, je, mb, kb, ti, tj, handle_dgemm
    2135            0 :             REAL(KIND=dp), ALLOCATABLE :: gram_t(:, :), phi_cols_t(:, :), phi_rows_t(:, :)
    2136            0 :             ALLOCATE (phi_rows_t(ntile, n_ao), phi_cols_t(n_ao, ntile), gram_t(ntile, ntile))
    2137            0 :             DO ib = 1, nrow_local, ntile
    2138            0 :                ie = MIN(ib + ntile - 1, nrow_local)
    2139            0 :                mb = ie - ib + 1
    2140              :                !$OMP PARALLEL DO DEFAULT(NONE) &
    2141              :                !$OMP SHARED(mb, n_ao, phi_rows_t, phi_local, row_indices, ib) &
    2142            0 :                !$OMP PRIVATE(ti, j_loc) SCHEDULE(STATIC)
    2143              :                DO j_loc = 1, n_ao
    2144              :                   DO ti = 1, mb
    2145              :                      phi_rows_t(ti, j_loc) = phi_local(row_indices(ib + ti - 1), j_loc)
    2146              :                   END DO
    2147              :                END DO
    2148              :                !$OMP END PARALLEL DO
    2149            0 :                DO jb = 1, ncol_local, ntile
    2150            0 :                   je = MIN(jb + ntile - 1, ncol_local)
    2151            0 :                   kb = je - jb + 1
    2152              :                   !$OMP PARALLEL DO DEFAULT(NONE) &
    2153              :                   !$OMP SHARED(kb, n_ao, phi_cols_t, phi_local, col_indices, jb) &
    2154            0 :                   !$OMP PRIVATE(tj, i_loc) SCHEDULE(STATIC)
    2155              :                   DO tj = 1, kb
    2156              :                      DO i_loc = 1, n_ao
    2157              :                         phi_cols_t(i_loc, tj) = phi_local(col_indices(jb + tj - 1), i_loc)
    2158              :                      END DO
    2159              :                   END DO
    2160              :                   !$OMP END PARALLEL DO
    2161            0 :                   CALL timeset(routineN//"_dgemm", handle_dgemm)
    2162              :                   CALL dgemm('N', 'N', mb, kb, n_ao, &
    2163              :                              1.0_dp, phi_rows_t, ntile, phi_cols_t, n_ao, &
    2164            0 :                              0.0_dp, gram_t, ntile)
    2165            0 :                   CALL timestop(handle_dgemm)
    2166              :                   !$OMP PARALLEL DO DEFAULT(NONE) &
    2167              :                   !$OMP SHARED(mb, kb, gram_t, d_vec, row_indices, col_indices, ib, jb) &
    2168              :                   !$OMP SHARED(local_data, tikhonov) &
    2169            0 :                   !$OMP PRIVATE(ti, tj, ig, jg) SCHEDULE(STATIC)
    2170              :                   DO tj = 1, kb
    2171              :                      jg = col_indices(jb + tj - 1)
    2172              :                      DO ti = 1, mb
    2173              :                         ig = row_indices(ib + ti - 1)
    2174              :                         local_data(ib + ti - 1, jb + tj - 1) = &
    2175              :                            gram_t(ti, tj)*gram_t(ti, tj)*d_vec(ig)*d_vec(jg)
    2176              :                         IF (ig == jg) THEN
    2177              :                            local_data(ib + ti - 1, jb + tj - 1) = &
    2178              :                               local_data(ib + ti - 1, jb + tj - 1) + tikhonov
    2179              :                         END IF
    2180              :                      END DO
    2181              :                   END DO
    2182              :                   !$OMP END PARALLEL DO
    2183              :                END DO
    2184              :             END DO
    2185            0 :             DEALLOCATE (phi_rows_t, phi_cols_t, gram_t)
    2186              :          END BLOCK
    2187              :       END IF
    2188              : 
    2189              :       ! Load the replicated d_lp into the block-cyclic fm_b
    2190            0 :       CALL cp_fm_set_submatrix(fm_b, d_lp)
    2191              : 
    2192              :       ! pdpotrf (Cholesky factorisation; cp_fm_cholesky_decompose
    2193              :       ! factors with UPLO='U', so pdpotrs must match)
    2194            0 :       CALL cp_fm_cholesky_decompose(fm_D, n=n_loc, info_out=info)
    2195            0 :       IF (info /= 0) THEN
    2196            0 :          CPABORT("pdpotrf failed in solve_D_lp_distributed")
    2197              :       END IF
    2198              : 
    2199              :       ! pdpotrs/dpotrs (solve in place on fm_b)
    2200            0 :       CALL cp_fm_cholesky_solve(fm_D, fm_b, n=n_loc, info_out=info)
    2201            0 :       IF (info /= 0) THEN
    2202            0 :          CPABORT("pdpotrs failed in solve_D_lp_distributed")
    2203              :       END IF
    2204              : 
    2205              :       ! Gather distributed solution back into the replicated d_lp
    2206            0 :       CALL cp_fm_get_submatrix(fm_b, d_lp)
    2207              : 
    2208            0 :       CALL cp_fm_release(fm_D)
    2209            0 :       CALL cp_fm_release(fm_b)
    2210            0 :       CALL cp_fm_struct_release(fm_struct_D)
    2211            0 :       CALL cp_fm_struct_release(fm_struct_b)
    2212              : 
    2213            0 :       CALL timestop(handle)
    2214              : 
    2215            0 :    END SUBROUTINE solve_D_lp_distributed
    2216              : 
    2217              : ! **************************************************************************************************
    2218              : !> \brief Computes the polarizability matrix in the RI basis for every
    2219              : !>        imaginary-time point:
    2220              : !>          G^occ/vir_μν(i|τ|) = Σ_n C_μn e^(-|(ϵ_n-ϵ_F)τ|) C_νn      (AO x AO, build_G_ao)
    2221              : !>          χ_ll'(iτ) = [Σ_μν Φ_μ(r_l) G^occ_μν Φ_ν(r_l')] ∘ [Σ_μν Φ_μ(r_l) G^vir_μν Φ_ν(r_l')]
    2222              : !>          χ_PQ(iτ)  = g_s Σ_ll' Z_lP χ_ll'(iτ) Z_l'Q                (g_s = spin degeneracy)
    2223              : !>        The grid (l) index is streamed in panels (contract_grid_panels).
    2224              : !> \param bs_env ...
    2225              : !> \param mat_chi_Gamma_tau ...
    2226              : !> \param mat_phi_mu_l ...
    2227              : !> \param mat_Z_lP ...
    2228              : ! **************************************************************************************************
    2229           20 :    SUBROUTINE get_mat_chi_Gamma_tau(bs_env, mat_chi_Gamma_tau, mat_phi_mu_l, mat_Z_lP)
    2230              : 
    2231              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2232              :       TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: mat_chi_Gamma_tau
    2233              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l, mat_Z_lP
    2234              : 
    2235              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_mat_chi_Gamma_tau'
    2236              : 
    2237              :       INTEGER                                            :: handle, i_t, ispin, n_panels
    2238           20 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    2239              :       REAL(KIND=dp)                                      :: grid_occ, t1, tau
    2240              :       TYPE(dbcsr_type)                                   :: matrix_G_occ_ao, matrix_G_vir_ao
    2241              : 
    2242           20 :       CALL timeset(routineN, handle)
    2243              : 
    2244              :       ! Panel boundaries for the grid-streaming contraction.
    2245              :       ! The panels are identical for χ, Σ^x and Σ^c, so the count is reported once here for all three stages.
    2246              : 
    2247           20 :       CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    2248              : 
    2249           20 :       n_panels = SIZE(pan_first)
    2250           20 :       IF (bs_env%unit_nr > 0) THEN
    2251           10 :          WRITE (bs_env%unit_nr, '(T2,A,I44)') 'Number of batches for χ, Σ matrices', n_panels
    2252           10 :          CALL m_flush(bs_env%unit_nr)
    2253              :       END IF
    2254              : 
    2255              :       ! =========================================================================
    2256              :       ! IMAGINARY TIME LOOP
    2257              :       ! χ_PQ(iτ) = Σ_s g_s · Z^T ( (φ G^occ_s φ^T) ∘ (φ G^vir_s φ^T) ) Z
    2258              :       ! (g_s = spin degeneracy)
    2259              :       ! =========================================================================
    2260          310 :       DO i_t = 1, bs_env%num_time_freq_points
    2261          290 :          t1 = m_walltime()
    2262          290 :          tau = bs_env%imag_time_points(i_t)
    2263              : 
    2264          640 :          DO ispin = 1, bs_env%n_spin
    2265              : 
    2266              :             ! AO-space Green's functions G^occ_µν, G^vir_µν (dense AO x AO, small)
    2267          350 :             CALL build_G_ao(bs_env, tau, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_G_occ_ao)
    2268          350 :             CALL build_G_ao(bs_env, tau, ispin, .FALSE., .TRUE., mat_phi_mu_l, matrix_G_vir_ao)
    2269              : 
    2270              :             ! χ_PQ += g_s · Z^T ( (φ G^occ φ^T) ∘ (φ G^vir φ^T) ) Z
    2271              :             CALL contract_grid_panels(L_A=mat_phi_mu_l, M_A=matrix_G_occ_ao, &
    2272              :                                       L_B=mat_phi_mu_l, M_B=matrix_G_vir_ao, &
    2273              :                                       L_out=mat_Z_lP, mat_out=mat_chi_Gamma_tau(i_t)%matrix, &
    2274              :                                       scale=bs_env%spin_degeneracy, eps=bs_env%eps_filter, &
    2275              :                                       para_env=bs_env%para_env, &
    2276              :                                       pan_first=pan_first, pan_last=pan_last, &
    2277              :                                       lb_eq_la=.TRUE., lout_eq_la=.FALSE., &
    2278              :                                       zero_out=(ispin == 1), &
    2279              :                                       keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
    2280              :                                       centroids=bs_env%ri_rs%chunk_centroids, &
    2281              :                                       cutoff=bs_env%ri_rs%cutoff_radius_v_w, &
    2282          350 :                                       grid_occupation=grid_occ)
    2283              : 
    2284          350 :             CALL dbcsr_release(matrix_G_occ_ao)
    2285          640 :             CALL dbcsr_release(matrix_G_vir_ao)
    2286              : 
    2287              :          END DO ! ispin
    2288              : 
    2289              :          ! Sparsity reports
    2290          290 :          IF (i_t == 1 .AND. bs_env%unit_nr > 0) THEN
    2291           10 :             WRITE (bs_env%unit_nr, '(A)') ' '
    2292              :             WRITE (bs_env%unit_nr, '(T2,A,F17.2,A)') &
    2293           10 :                'Occupation of non-zero elements of G(l,l''), χ(l,l''), W(l,l'')', &
    2294           20 :                grid_occ*100.0_dp, ' %'
    2295           10 :             WRITE (bs_env%unit_nr, '(A)') ' '
    2296           10 :             CALL m_flush(bs_env%unit_nr)
    2297              :          END IF
    2298          290 :          IF (i_t == 1) then
    2299              :             CALL print_matrix_occupation(mat_chi_Gamma_tau(i_t)%matrix, 'χ(P,Q)', &
    2300           20 :                                          bs_env%para_env, bs_env%unit_nr)
    2301              :          end if
    2302              : 
    2303          310 :          IF (bs_env%unit_nr > 0) THEN
    2304              :             WRITE (bs_env%unit_nr, '(T2,A,I13,A,I3,A,F7.1,A)') &
    2305          145 :                'Computed χ(iτ,k=0) for time point', i_t, ' /', bs_env%num_time_freq_points, &
    2306          290 :                ',    Execution time', m_walltime() - t1, ' s'
    2307              :          END IF
    2308              : 
    2309              :       END DO ! i_t
    2310              : 
    2311           20 :       IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
    2312              : 
    2313           20 :       CALL timestop(handle)
    2314              : 
    2315           40 :    END SUBROUTINE get_mat_chi_Gamma_tau
    2316              : 
    2317              : ! **************************************************************************************************
    2318              : !> \brief Prints the non-zero occupation percentage of a DBCSR matrix on one line.
    2319              : !> \param matrix ...
    2320              : !> \param label ...
    2321              : !> \param para_env ...
    2322              : !> \param unit_nr ...
    2323              : ! **************************************************************************************************
    2324           74 :    SUBROUTINE print_matrix_occupation(matrix, label, para_env, unit_nr)
    2325              : 
    2326              :       TYPE(dbcsr_type), INTENT(IN)                       :: matrix
    2327              :       CHARACTER(LEN=*), INTENT(IN)                       :: label
    2328              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    2329              :       INTEGER, INTENT(IN)                                :: unit_nr
    2330              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_matrix_occupation'
    2331              : 
    2332              :       INTEGER                                            :: handle
    2333              : 
    2334              :       REAL(KIND=dp)                                      :: frac_2p31, max_loc, occ
    2335              : 
    2336           74 :       CALL timeset(routineN, handle)
    2337              : 
    2338           74 :       occ = dbcsr_get_occupation(matrix)
    2339           74 :       max_loc = REAL(dbcsr_get_data_size(matrix), dp)
    2340           74 :       CALL para_env%max(max_loc)
    2341              : 
    2342           74 :       IF (unit_nr > 0) THEN
    2343           37 :          frac_2p31 = max_loc/REAL(dbcsr_msg_elem_limit, dp)
    2344           37 :          WRITE (unit_nr, '(A)') ' '
    2345              :          WRITE (unit_nr, '(T2,A,F36.2,A)') &
    2346           37 :             'Occupation of non-zero elements of '//TRIM(label), occ*100.0_dp, ' %'
    2347           37 :          IF (frac_2p31 > 0.5_dp) then
    2348            0 :             WRITE (unit_nr, '(T4,A)') '*** WARNING: max/rank approaching 2^31 -- DBCSR overflow risk ***'
    2349              :          end if
    2350           37 :          CALL m_flush(unit_nr)
    2351              :       END IF
    2352              : 
    2353           74 :       CALL timestop(handle)
    2354              : 
    2355           74 :    END SUBROUTINE print_matrix_occupation
    2356              : 
    2357              : ! **************************************************************************************************
    2358              : !> \brief Marks the grid blocks whose centroid lies within cutoff of the bounding box of the
    2359              : !>        panel [blk0, blk1]'s chunk centroids.
    2360              : !> \param centroids ...
    2361              : !> \param blk0 ...
    2362              : !> \param blk1 ...
    2363              : !> \param cutoff ...
    2364              : !> \param used ...
    2365              : ! **************************************************************************************************
    2366           92 :    SUBROUTINE mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, used)
    2367              : 
    2368              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centroids
    2369              :       INTEGER, INTENT(IN)                                :: blk0, blk1
    2370              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff
    2371              :       LOGICAL, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: used
    2372              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'mask_grid_blocks_near_panel'
    2373              : 
    2374              :       INTEGER                                            :: handle
    2375              :       INTEGER                                            :: c, k
    2376              :       REAL(KIND=dp)                                      :: cutoff2, d2, dx
    2377              :       REAL(KIND=dp), DIMENSION(3)                        :: hi, lo
    2378              : 
    2379           92 :       CALL timeset(routineN, handle)
    2380              : 
    2381           92 :       cutoff2 = cutoff**2
    2382           92 :       lo(:) = MINVAL(centroids(:, blk0:blk1), DIM=2)
    2383           92 :       hi(:) = MAXVAL(centroids(:, blk0:blk1), DIM=2)
    2384              : 
    2385          276 :       ALLOCATE (used(SIZE(centroids, 2)))
    2386         2024 :       DO c = 1, SIZE(centroids, 2)
    2387              :          d2 = 0.0_dp
    2388         7728 :          DO k = 1, 3
    2389         5796 :             dx = MAX(0.0_dp, lo(k) - centroids(k, c), centroids(k, c) - hi(k))
    2390         7728 :             d2 = d2 + dx*dx
    2391              :          END DO
    2392         2024 :          used(c) = (d2 <= cutoff2)
    2393              :       END DO
    2394              : 
    2395           92 :       CALL timestop(handle)
    2396              : 
    2397           92 :    END SUBROUTINE mask_grid_blocks_near_panel
    2398              : 
    2399              : ! **************************************************************************************************
    2400              : !> \brief Exact allocated-element count of the geo template of panel [blk0, blk1]: the very same
    2401              : !>        per-block-pair centroid test as build_geo_template_panel, so this is the true DBCSR
    2402              : !>        data size of A_pan/B_pan/C_pan (DBCSR stores whole blocks).
    2403              : !> \param r_blk_sizes ...
    2404              : !> \param centroids ...
    2405              : !> \param used ...
    2406              : !> \param blk0 ...
    2407              : !> \param blk1 ...
    2408              : !> \param cutoff ...
    2409              : !> \param nze_tmpl ...
    2410              : ! **************************************************************************************************
    2411            8 :    SUBROUTINE panel_template_elems(r_blk_sizes, centroids, used, blk0, blk1, cutoff, nze_tmpl)
    2412              : 
    2413              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes
    2414              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centroids
    2415              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: used
    2416              :       INTEGER, INTENT(IN)                                :: blk0, blk1
    2417              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff
    2418              :       INTEGER(KIND=int_8), INTENT(OUT)                   :: nze_tmpl
    2419              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'panel_template_elems'
    2420              : 
    2421              :       INTEGER                                            :: handle
    2422              :       INTEGER                                            :: c, ib, n_used
    2423            8 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: used_idx
    2424              :       REAL(KIND=dp)                                      :: cutoff2
    2425              : 
    2426            8 :       CALL timeset(routineN, handle)
    2427              : 
    2428              :       ! Compress the near mask once so the pair loop only visits candidate columns.
    2429          176 :       n_used = COUNT(used)
    2430           24 :       ALLOCATE (used_idx(n_used))
    2431            8 :       n_used = 0
    2432          176 :       DO c = 1, SIZE(used)
    2433          176 :          IF (used(c)) THEN
    2434          168 :             n_used = n_used + 1
    2435          168 :             used_idx(n_used) = c
    2436              :          END IF
    2437              :       END DO
    2438              : 
    2439            8 :       cutoff2 = cutoff**2
    2440            8 :       nze_tmpl = 0_int_8
    2441              :       !$OMP PARALLEL DO DEFAULT(NONE) SHARED(blk0, blk1, n_used, used_idx, centroids, cutoff2, &
    2442            8 :       !$OMP r_blk_sizes) PRIVATE(ib, c) REDUCTION(+:nze_tmpl)
    2443              :       DO ib = blk0, blk1
    2444              :          DO c = 1, n_used
    2445              :             IF (SUM((centroids(:, ib) - centroids(:, used_idx(c)))**2) <= cutoff2) then
    2446              :                nze_tmpl = nze_tmpl + INT(r_blk_sizes(ib), int_8)*INT(r_blk_sizes(used_idx(c)), int_8)
    2447              :             end if
    2448              :          END DO
    2449              :       END DO
    2450              :       !$OMP END PARALLEL DO
    2451              : 
    2452            8 :       CALL timestop(handle)
    2453              : 
    2454           16 :    END SUBROUTINE panel_template_elems
    2455              : 
    2456              : ! **************************************************************************************************
    2457              : !> \brief Per-rank peak memory (GB) of one panel step of the neighborhood-restricted
    2458              : !>        contractions: three grid x grid panels of the template size (A_pan, B_pan, C_pan)
    2459              : !>        plus the grid x RI intermediates (tmp2 and the accumulation operand) and the
    2460              : !>        grid x AO intermediate (tmpA), whose column support is the panel's geometric
    2461              : !>        neighborhood fraction f_near = width/n_grid. Shared by the panel planner and
    2462              : !> \param nze_tmpl ...
    2463              : !> \param pan_rows ...
    2464              : !> \param width ...
    2465              : !> \param n_grid_total ...
    2466              : !> \param n_RI ...
    2467              : !> \param n_ao ...
    2468              : !> \param n_procs ...
    2469              : !> \param mem_GB ...
    2470              : ! **************************************************************************************************
    2471            8 :    SUBROUTINE panel_mem_estimate_GB(nze_tmpl, pan_rows, width, n_grid_total, n_RI, n_ao, &
    2472              :                                     n_procs, mem_GB)
    2473              : 
    2474              :       INTEGER(KIND=int_8), INTENT(IN)                    :: nze_tmpl
    2475              :       INTEGER, INTENT(IN)                                :: pan_rows, width, n_grid_total, n_RI, &
    2476              :                                                             n_ao, n_procs
    2477              :       REAL(KIND=dp), INTENT(OUT)                         :: mem_GB
    2478              : 
    2479              :       REAL(KIND=dp)                                      :: f_near
    2480              : 
    2481            8 :       f_near = REAL(width, dp)/REAL(MAX(n_grid_total, 1), dp)
    2482              :       mem_GB = (3.0_dp*REAL(nze_tmpl, dp) + &
    2483              :                 REAL(pan_rows, dp)*f_near*(2.0_dp*REAL(n_RI, dp) + REAL(n_ao, dp)))* &
    2484            8 :                8.0_dp/REAL(MAX(n_procs, 1), dp)*1.0E-9_dp
    2485              : 
    2486            8 :    END SUBROUTINE panel_mem_estimate_GB
    2487              : 
    2488              : ! **************************************************************************************************
    2489              : !> \brief Plans the panel boundaries for the streaming contractions. Panels grow by whole grid
    2490              : !>        row-blocks towards ~panel_size rows. When the neighborhood restriction is active
    2491              : !>        (centroids+cutoff), each candidate panel is additionally checked against
    2492              : !>          (a) the 32-bit message bound with the panel's TRUE occupancy
    2493              : !>          (b) the per-rank memory budget: panel_mem_estimate_GB <= mem_budget_GB.
    2494              : !> \param r_blk_sizes ...
    2495              : !> \param panel_size ...
    2496              : !> \param min_dim ...
    2497              : !> \param pan_first ...
    2498              : !> \param pan_last ...
    2499              : !> \param centroids ...
    2500              : !> \param cutoff ...
    2501              : !> \param n_RI ...
    2502              : !> \param n_ao ...
    2503              : !> \param n_procs ...
    2504              : !> \param mem_budget_GB ...
    2505              : ! **************************************************************************************************
    2506           20 :    SUBROUTINE plan_grid_panels(r_blk_sizes, panel_size, min_dim, pan_first, pan_last, &
    2507           20 :                                centroids, cutoff, n_RI, n_ao, n_procs, mem_budget_GB, &
    2508              :                                honor_exact, unsafe)
    2509              : 
    2510              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes
    2511              :       INTEGER, INTENT(IN)                                :: panel_size, min_dim
    2512              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: pan_first, pan_last
    2513              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), OPTIONAL :: centroids
    2514              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: cutoff
    2515              :       INTEGER, INTENT(IN), OPTIONAL                      :: n_RI, n_ao, n_procs
    2516              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: mem_budget_GB
    2517              :       LOGICAL, INTENT(IN), OPTIONAL                      :: honor_exact
    2518              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: unsafe
    2519              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'plan_grid_panels'
    2520              : 
    2521              :       INTEGER                                            :: blk0, blk1, ib, n_grid_blocks, &
    2522              :                                                             n_grid_total, n_panels, rows_acc, &
    2523              :                                                             target, width
    2524              :       INTEGER(KIND=int_8)                                :: msg, nze_tmpl, side
    2525           20 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: tmp_first, tmp_last
    2526              :       LOGICAL                                            :: fits, my_honor_exact, my_unsafe, &
    2527              :                                                             use_cutoff
    2528           20 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: used
    2529              :       REAL(KIND=dp)                                      :: f_near, mem_GB
    2530              :       INTEGER                                            :: handle
    2531              : 
    2532           20 :       CALL timeset(routineN, handle)
    2533              : 
    2534           20 :       use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
    2535           20 :       IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
    2536            2 :       IF (use_cutoff) THEN
    2537            2 :          CPASSERT(PRESENT(n_RI) .AND. PRESENT(n_ao) .AND. PRESENT(n_procs))
    2538              :       END IF
    2539              : 
    2540              :       ! honor_exact: use N_PANELS as requested -- do NOT split a panel further even if it trips
    2541              :       ! the message-overflow / memory-budget check; instead flag `unsafe` so the caller can warn.
    2542           20 :       my_honor_exact = .FALSE.
    2543           20 :       IF (PRESENT(honor_exact)) my_honor_exact = honor_exact
    2544           20 :       my_unsafe = .FALSE.
    2545              : 
    2546           20 :       n_grid_blocks = SIZE(r_blk_sizes)
    2547          358 :       n_grid_total = SUM(r_blk_sizes)
    2548           80 :       ALLOCATE (tmp_first(n_grid_blocks), tmp_last(n_grid_blocks))
    2549              : 
    2550           20 :       n_panels = 0
    2551           20 :       blk0 = 1
    2552           42 :       DO WHILE (blk0 <= n_grid_blocks)
    2553           22 :          target = panel_size
    2554            0 :          DO
    2555           22 :             rows_acc = 0
    2556           22 :             blk1 = blk0
    2557          340 :             DO ib = blk0, n_grid_blocks
    2558          338 :                rows_acc = rows_acc + r_blk_sizes(ib)
    2559          338 :                blk1 = ib
    2560          340 :                IF (rows_acc >= target) EXIT
    2561              :             END DO
    2562           22 :             IF (.NOT. use_cutoff .OR. blk1 == blk0) EXIT
    2563            4 :             CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, used)
    2564           88 :             width = SUM(r_blk_sizes, MASK=used)
    2565            4 :             CALL panel_template_elems(r_blk_sizes, centroids, used, blk0, blk1, cutoff, nze_tmpl)
    2566            4 :             f_near = REAL(width, dp)/REAL(MAX(n_grid_total, 1), dp)
    2567            4 :             side = INT(REAL(rows_acc, dp)*f_near*REAL(MAX(n_RI, n_ao), dp), int_8)
    2568            4 :             msg = MAX(nze_tmpl, side)/INT(MAX(min_dim, 1), int_8)
    2569            4 :             fits = (msg <= dbcsr_msg_elem_limit/4)
    2570            4 :             IF (fits .AND. PRESENT(mem_budget_GB)) THEN
    2571            4 :                IF (mem_budget_GB > 0.0_dp) THEN
    2572              :                   CALL panel_mem_estimate_GB(nze_tmpl, rows_acc, width, n_grid_total, &
    2573            4 :                                              n_RI, n_ao, n_procs, mem_GB)
    2574            4 :                   fits = (mem_GB <= mem_budget_GB)
    2575              :                END IF
    2576              :             END IF
    2577              :             ! Panel size is bounded only by the message-overflow and memory checks above; there is
    2578              :             ! no neighborhood-width (f_near) cap. mp_waitall is dominated by the NUMBER of panel
    2579              :             ! multiplies, so fewer/larger panels are cheaper here -- panel count is driven DOWN by
    2580              :             ! the N_PANELS keyword (panel_size), not split up by a width heuristic.
    2581            4 :             IF (my_honor_exact) THEN
    2582              :                ! Keep exactly the requested grouping; just record if it exceeds a safety limit.
    2583            4 :                IF (.NOT. fits) my_unsafe = .TRUE.
    2584              :                EXIT
    2585              :             END IF
    2586            0 :             IF (fits) EXIT
    2587           18 :             target = MAX(1, MIN(target, rows_acc)/2)
    2588              :          END DO
    2589           22 :          n_panels = n_panels + 1
    2590           22 :          tmp_first(n_panels) = blk0
    2591           22 :          tmp_last(n_panels) = blk1
    2592           22 :          blk0 = blk1 + 1
    2593              :       END DO
    2594              : 
    2595           80 :       ALLOCATE (pan_first(n_panels), pan_last(n_panels))
    2596           42 :       pan_first(:) = tmp_first(1:n_panels)
    2597           42 :       pan_last(:) = tmp_last(1:n_panels)
    2598           20 :       DEALLOCATE (tmp_first, tmp_last)
    2599              : 
    2600           20 :       IF (PRESENT(unsafe)) unsafe = my_unsafe
    2601              : 
    2602           20 :       CALL timestop(handle)
    2603              : 
    2604           40 :    END SUBROUTINE plan_grid_panels
    2605              : 
    2606              : ! **************************************************************************************************
    2607              : !> \brief Resolves the panel boundaries for the streaming contractions from the bs_env settings:
    2608              : !> \param bs_env ...
    2609              : !> \param mat_phi_mu_l ...
    2610              : !> \param pan_first ...
    2611              : !> \param pan_last ...
    2612              : ! **************************************************************************************************
    2613           80 :    SUBROUTINE resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    2614              : 
    2615              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2616              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
    2617              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: pan_first, pan_last
    2618              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'resolve_grid_panels'
    2619              : 
    2620              :       INTEGER                                            :: min_dim, n_grid_total, n_panels_req, &
    2621              :                                                             npcols, nprows, panel_size, safe_max
    2622           80 :       INTEGER, DIMENSION(:), POINTER                     :: r_blk_sizes
    2623              :       LOGICAL                                            :: honor_exact, panels_unsafe, use_cutoff
    2624              :       REAL(KIND=dp)                                      :: mem_avail_GB, mem_budget_GB
    2625              :       TYPE(dbcsr_distribution_type)                      :: dist
    2626              :       INTEGER                                            :: handle
    2627              : 
    2628           80 :       CALL timeset(routineN, handle)
    2629              : 
    2630           80 :       IF (ALLOCATED(bs_env%ri_rs%pan_first)) THEN
    2631          246 :          ALLOCATE (pan_first, SOURCE=bs_env%ri_rs%pan_first)
    2632          246 :          ALLOCATE (pan_last, SOURCE=bs_env%ri_rs%pan_last)
    2633           60 :          CALL timestop(handle)
    2634              :          RETURN
    2635              :       END IF
    2636              : 
    2637              :       use_cutoff = bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .AND. &
    2638           20 :                    ALLOCATED(bs_env%ri_rs%chunk_centroids)
    2639              : 
    2640              :       ! MIN(nprows, npcols) is the divisor that bounds the worst-rank Cannon message: a
    2641              :       ! P x n_grid panel is replicated into block row strips (P/nprows x n_grid) or column
    2642              :       ! strips (P x n_grid/npcols) during multiply_cannon, so the largest single-rank
    2643              :       ! message is ~ P*n_grid / MIN(nprows,npcols) elements.
    2644              :       CALL dbcsr_get_info(mat_phi_mu_l, nfullrows_total=n_grid_total, row_blk_size=r_blk_sizes, &
    2645           20 :                           distribution=dist)
    2646           20 :       CALL dbcsr_distribution_get(dist, nprows=nprows, npcols=npcols)
    2647           20 :       min_dim = MAX(MIN(nprows, npcols), 1)
    2648              : 
    2649              :       ! Panel height such that NO per-rank DBCSR message can overflow the 32-bit length field
    2650              :       ! (see dbcsr_msg_elem_limit): requiring the worst-rank message to stay under
    2651              :       ! 0.5 * HUGE(int_4) gives the safe height P_safe = 0.5 * HUGE(int_4) * min_dim / n_grid.
    2652           20 :       IF (use_cutoff) THEN
    2653            2 :          safe_max = n_grid_total
    2654              :       ELSE
    2655              :          safe_max = INT(0.5_dp*REAL(dbcsr_msg_elem_limit, dp)*REAL(min_dim, dp)/ &
    2656           18 :                         REAL(n_grid_total, dp))
    2657           18 :          safe_max = MAX(1, MIN(safe_max, n_grid_total))
    2658              :       END IF
    2659              : 
    2660              :       ! A user-set N_PANELS ( > 1 ) is honored EXACTLY: the planner produces that many panels
    2661              :       ! (up to grid-block granularity) and never force-splits them for the message/memory safety
    2662              :       ! limits -- if a limit is tripped it warns instead of silently changing the count.
    2663           20 :       n_panels_req = bs_env%ri_rs%n_panels
    2664           20 :       honor_exact = (n_panels_req > 1)
    2665           20 :       panels_unsafe = .FALSE.
    2666           20 :       IF (n_panels_req > 1) THEN
    2667              :          ! ceil(n_grid/n_panels_req) rows per panel => exactly n_panels_req panels. With the
    2668              :          ! cutoff active safe_max = n_grid_total (no clamp, honored exactly); without it, safe_max
    2669              :          ! is the int32-overflow ceiling and MUST still bound the panel (the non-cutoff planner
    2670              :          ! loop has no in-loop message-size check).
    2671            2 :          panel_size = MIN((n_grid_total + n_panels_req - 1)/n_panels_req, safe_max)
    2672              :       ELSE
    2673              :          ! Default (<= 1): a single whole-grid panel, clamped to the overflow-safe ceiling.
    2674           18 :          panel_size = safe_max
    2675              :       END IF
    2676           20 :       panel_size = MAX(1, panel_size)
    2677              : 
    2678           20 :       IF (use_cutoff) THEN
    2679              :          ! Half of the measured free memory as panel budget.
    2680            2 :          CALL ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)
    2681            2 :          mem_budget_GB = 0.5_dp*mem_avail_GB
    2682              :          CALL plan_grid_panels(r_blk_sizes, panel_size, min_dim, pan_first, pan_last, &
    2683              :                                centroids=bs_env%ri_rs%chunk_centroids, &
    2684              :                                cutoff=bs_env%ri_rs%cutoff_radius_v_w, &
    2685              :                                n_RI=bs_env%n_RI, n_ao=bs_env%n_ao, &
    2686              :                                n_procs=bs_env%para_env%num_pe, mem_budget_GB=mem_budget_GB, &
    2687            2 :                                honor_exact=honor_exact, unsafe=panels_unsafe)
    2688              :       ELSE
    2689           18 :          CALL plan_grid_panels(r_blk_sizes, panel_size, min_dim, pan_first, pan_last)
    2690              :       END IF
    2691              : 
    2692           20 :       IF (honor_exact .AND. panels_unsafe .AND. bs_env%unit_nr > 0) THEN
    2693              :          WRITE (bs_env%unit_nr, '(T2,A,I0,A)') &
    2694            0 :             '*** WARNING: N_PANELS = ', n_panels_req, ' used as requested, but one or more '// &
    2695              :             'panels exceed the DBCSR 32-bit message / memory-budget safety limit. The run may '// &
    2696            0 :             'abort or swap; increase N_PANELS if it does. ***'
    2697              :       END IF
    2698              : 
    2699           82 :       ALLOCATE (bs_env%ri_rs%pan_first, SOURCE=pan_first)
    2700           82 :       ALLOCATE (bs_env%ri_rs%pan_last, SOURCE=pan_last)
    2701              : 
    2702           20 :       CALL timestop(handle)
    2703              : 
    2704          100 :    END SUBROUTINE resolve_grid_panels
    2705              : 
    2706              : ! **************************************************************************************************
    2707              : !> \brief Available memory per MPI process (GB). /proc/meminfo reports node-wide memory, so
    2708              : !>        every rank on a node reads the SAME MemLikelyFree; the per-process share is
    2709              : !>        node_free / ranks_per_node (ranks grouped by a hostname hash exchanged via allgather).
    2710              : !>        Returns the MIN across all ranks (most-constrained node)
    2711              : !> \param bs_env ...
    2712              : !> \param mem_avail_GB ...
    2713              : ! **************************************************************************************************
    2714           48 :    SUBROUTINE ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)
    2715              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2716              :       REAL(KIND=dp), INTENT(OUT)                         :: mem_avail_GB
    2717              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'ri_rs_mem_avail_per_proc_GB'
    2718              : 
    2719              :       CHARACTER(LEN=default_string_length)               :: hostname
    2720              :       INTEGER                                            :: host_hash, ic, n_procs, ranks_per_node
    2721              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: all_host_hashes
    2722              :       INTEGER(KIND=int_8)                                :: h8, mem_buffers, mem_cached, mem_free, &
    2723              :                                                             mem_likely_free, mem_sreclaimable, &
    2724              :                                                             mem_slab, mem_total
    2725              :       INTEGER                                            :: handle
    2726              : 
    2727           48 :       CALL timeset(routineN, handle)
    2728              : 
    2729           48 :       n_procs = bs_env%para_env%num_pe
    2730           48 :       CALL m_hostnm(hostname)
    2731           48 :       h8 = 0_int_8
    2732          624 :       DO ic = 1, LEN_TRIM(hostname)
    2733          624 :          h8 = MOD(h8*127_int_8 + INT(ICHAR(hostname(ic:ic)), int_8), 2147483647_int_8)
    2734              :       END DO
    2735           48 :       host_hash = INT(h8)
    2736          144 :       ALLOCATE (all_host_hashes(n_procs))
    2737           48 :       CALL bs_env%para_env%allgather(host_hash, all_host_hashes)
    2738          144 :       ranks_per_node = MAX(COUNT(all_host_hashes == host_hash), 1)
    2739           48 :       DEALLOCATE (all_host_hashes)
    2740              : 
    2741              :       CALL m_memory_details(MemTotal=mem_total, MemFree=mem_free, Buffers=mem_buffers, &
    2742              :                             Cached=mem_cached, Slab=mem_slab, SReclaimable=mem_sreclaimable, &
    2743           48 :                             MemLikelyFree=mem_likely_free)
    2744           48 :       mem_avail_GB = REAL(mem_likely_free, dp)*1.0E-9_dp/REAL(ranks_per_node, dp)
    2745           48 :       CALL bs_env%para_env%min(mem_avail_GB)
    2746              : 
    2747           48 :       CALL timestop(handle)
    2748              : 
    2749           48 :    END SUBROUTINE ri_rs_mem_avail_per_proc_GB
    2750              : 
    2751              : ! **************************************************************************************************
    2752              : !> \brief Estimates and prints per-process memory requirements for the RI-RS GW calculation.
    2753              : !> \param qs_env ...
    2754              : !> \param bs_env ...
    2755              : ! **************************************************************************************************
    2756           20 :    SUBROUTINE print_ri_rs_memory_estimate(qs_env, bs_env)
    2757              : 
    2758              : !$    USE OMP_LIB, ONLY: omp_get_max_threads
    2759              : 
    2760              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2761              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2762              : 
    2763              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_ri_rs_memory_estimate'
    2764              : 
    2765              :       INTEGER                                            :: handle, iatom, ipan, l, &
    2766              :                                                             max_n_local_grid, &
    2767              :                                                             n_ao_total, n_grid_total, &
    2768              :                                                             n_local_grid, n_loc_ri_max, n_procs, &
    2769              :                                                             n_procs_per_atom, n_RI, n_threads, &
    2770              :                                                             natom, pan_rows, pan_width
    2771              :       INTEGER(KIND=int_8)                                :: nze_tmpl
    2772           20 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    2773           20 :       INTEGER, DIMENSION(:), POINTER                     :: r_blk_sizes
    2774              :       LOGICAL                                            :: use_cutoff
    2775           20 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: grid_used
    2776              :       REAL(KIND=dp)                                      :: cutoff_ri, mem_avail_GB, mem_D_local_GB, &
    2777              :                                                             mem_dlp_GB, mem_pan_GB, mem_panels_GB, &
    2778              :                                                             mem_phi_local_GB, mem_Z_lP_GB, &
    2779              :                                                             mem_Zlp_peak_GB, pos_P(3)
    2780           20 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2781              : 
    2782           20 :       CALL timeset(routineN, handle)
    2783              : 
    2784              :       CALL dbcsr_get_info(bs_env%ri_rs%mat_phi_mu_l, nfullrows_total=n_grid_total, &
    2785           20 :                           row_blk_size=r_blk_sizes)
    2786           20 :       n_RI = bs_env%n_RI
    2787           20 :       n_procs = bs_env%para_env%num_pe
    2788              : 
    2789              :       ! Z_lP upper bound: dense n_grid × n_RI, distributed evenly across all ranks.
    2790              :       ! The actual sparse Z_lP is smaller due to the per-atom locality cutoff.
    2791              :       mem_Z_lP_GB = REAL(n_grid_total, dp)*REAL(n_RI, dp)*8.0_dp/ &
    2792           20 :                     REAL(n_procs, dp)*1.0E-9_dp
    2793              : 
    2794              :       ! Peak panel memory during Σ^c: two G panels (A_occ, A_vir) + one W panel plus the
    2795              :       ! grid × RI / grid × AO intermediates. With the CUTOFF_RADIUS_RL_W restriction the panel
    2796              :       ! matrices only allocate the geo-template blocks, so use the same nze-aware model as the
    2797              :       ! panel planner (panel_mem_estimate_GB); without the cutoff, dense panel_rows × n_grid.
    2798              :       ! Plus the n_RI × n_RI W_aux matrix. Distributed over n_procs ranks.
    2799              :       use_cutoff = bs_env%ri_rs%cutoff_radius_v_w > 0.0_dp .AND. &
    2800           20 :                    ALLOCATED(bs_env%ri_rs%chunk_centroids)
    2801           20 :       CALL resolve_grid_panels(bs_env, bs_env%ri_rs%mat_phi_mu_l, pan_first, pan_last)
    2802           20 :       mem_panels_GB = 0.0_dp
    2803           42 :       DO ipan = 1, SIZE(pan_first)
    2804          360 :          pan_rows = SUM(r_blk_sizes(pan_first(ipan):pan_last(ipan)))
    2805           22 :          IF (use_cutoff) THEN
    2806              :             CALL mask_grid_blocks_near_panel(bs_env%ri_rs%chunk_centroids, pan_first(ipan), &
    2807              :                                              pan_last(ipan), bs_env%ri_rs%cutoff_radius_v_w, &
    2808            4 :                                              grid_used)
    2809           88 :             pan_width = SUM(r_blk_sizes, MASK=grid_used)
    2810              :             CALL panel_template_elems(r_blk_sizes, bs_env%ri_rs%chunk_centroids, &
    2811              :                                       grid_used, pan_first(ipan), pan_last(ipan), &
    2812            4 :                                       bs_env%ri_rs%cutoff_radius_v_w, nze_tmpl)
    2813              :             CALL panel_mem_estimate_GB(nze_tmpl, pan_rows, pan_width, n_grid_total, &
    2814            4 :                                        n_RI, bs_env%n_ao, n_procs, mem_pan_GB)
    2815              :          ELSE
    2816           18 :             pan_width = n_grid_total
    2817              :             mem_pan_GB = (3.0_dp*REAL(pan_rows, dp)*REAL(pan_width, dp) + &
    2818              :                           2.0_dp*REAL(pan_rows, dp)*REAL(n_RI, dp))* &
    2819           18 :                          8.0_dp/REAL(n_procs, dp)*1.0E-9_dp
    2820              :          END IF
    2821           42 :          mem_panels_GB = MAX(mem_panels_GB, mem_pan_GB)
    2822              :       END DO
    2823              :       mem_panels_GB = mem_panels_GB + &
    2824           20 :                       REAL(n_RI, dp)*REAL(n_RI, dp)*8.0_dp/REAL(n_procs, dp)*1.0E-9_dp
    2825              : 
    2826              :       ! Z_lP SOLVE peak (compute_coeff_Z_lP). For the atom P with the largest integration
    2827              :       ! sphere, one rank holds simultaneously:
    2828              :       !   D_local  : n_local_grid x n_local_grid  (dense Gram, BLAS path only; O(n_local_grid^2))
    2829              :       !   phi_local: n_local_grid x n_ao_total
    2830              :       !   d_lp     : n_local_grid x n_loc_ri, replicated once + one private copy per OMP thread
    2831              :       ! n_local_grid = # grid points within cutoff_ri(P) = CUTOFF_RADIUS_RL_RI (if > 0) else
    2832              :       ! r_c(RI metric) + r_RI(P). This is NOT evenly distributed: n_local_grid depends on the
    2833              :       ! local density of atoms/grid, so the rank owning the densest atom peaks well above the
    2834              :       ! average. We report the worst-case (max over atoms) as a per-rank upper bound.
    2835           20 :       CALL get_qs_env(qs_env, particle_set=particle_set)
    2836           20 :       natom = SIZE(particle_set)
    2837           20 :       n_ao_total = bs_env%i_ao_end_from_atom(natom)
    2838              : 
    2839           20 :       max_n_local_grid = 0
    2840           20 :       n_loc_ri_max = 0
    2841           70 :       DO iatom = 1, natom
    2842           50 :          IF (bs_env%ri_rs%cutoff_radius_ri_rs > 0.0_dp) THEN
    2843              :             cutoff_ri = bs_env%ri_rs%cutoff_radius_ri_rs
    2844              :          ELSE
    2845           44 :             cutoff_ri = bs_env%ri_metric%cutoff_radius + bs_env%ri_rs%radius_ri_per_atom(iatom)
    2846              :          END IF
    2847          200 :          pos_P(:) = particle_set(iatom)%r(:)
    2848              :          n_local_grid = 0
    2849        25578 :          DO l = 1, n_grid_total
    2850       102162 :             IF (SUM((bs_env%ri_rs%grid_points(1:3, l) - pos_P(1:3))**2) <= cutoff_ri**2) then
    2851        24142 :                n_local_grid = n_local_grid + 1
    2852              :             end if
    2853              :          END DO
    2854           50 :          max_n_local_grid = MAX(max_n_local_grid, n_local_grid)
    2855              :          n_loc_ri_max = MAX(n_loc_ri_max, &
    2856           70 :                             bs_env%i_RI_end_from_atom(iatom) - bs_env%i_RI_start_from_atom(iatom) + 1)
    2857              :       END DO
    2858              : 
    2859           20 :       n_procs_per_atom = MIN(MAX(bs_env%ri_rs%n_procs_per_atom_z_lp, 1), n_procs)
    2860           20 :       n_threads = 1
    2861           20 : !$    n_threads = omp_get_max_threads()
    2862              : 
    2863              :       ! D_local: dense on one rank for the BLAS path; block-cyclic over the subgroup (=> /G) for
    2864              :       ! the ScaLAPACK path (N_PROCS_PER_ATOM_Z_LP = G > 1). phi_local/d_lp stay per-rank either way.
    2865           20 :       IF (n_procs_per_atom > 1) THEN
    2866            0 :          mem_D_local_GB = REAL(max_n_local_grid, dp)**2*8.0_dp/REAL(n_procs_per_atom, dp)*1.0E-9_dp
    2867              :       ELSE
    2868           20 :          mem_D_local_GB = REAL(max_n_local_grid, dp)**2*8.0_dp*1.0E-9_dp
    2869              :       END IF
    2870           20 :       mem_phi_local_GB = REAL(max_n_local_grid, dp)*REAL(n_ao_total, dp)*8.0_dp*1.0E-9_dp
    2871              :       mem_dlp_GB = REAL(max_n_local_grid, dp)*REAL(n_loc_ri_max, dp)*8.0_dp* &
    2872           20 :                    REAL(1 + n_threads, dp)*1.0E-9_dp
    2873           20 :       mem_Zlp_peak_GB = mem_D_local_GB + mem_phi_local_GB + mem_dlp_GB
    2874              : 
    2875              :       ! Available memory per process = node MemLikelyFree / ranks-per-node, min across ranks
    2876              :       ! (0 on non-Linux => warnings suppressed below).
    2877           20 :       CALL ri_rs_mem_avail_per_proc_GB(bs_env, mem_avail_GB)
    2878              : 
    2879           20 :       IF (bs_env%unit_nr > 0) THEN
    2880           10 :          WRITE (bs_env%unit_nr, '(A)') ' '
    2881           10 :          WRITE (bs_env%unit_nr, '(T2,A)') 'RI-RS memory estimate per MPI process:'
    2882              :          WRITE (bs_env%unit_nr, '(T4,A,F37.2,A)') &
    2883           10 :             'Available memory per process (system)', mem_avail_GB, ' GB'
    2884              :          WRITE (bs_env%unit_nr, '(T4,A,F18.2,A)') &
    2885           10 :             'Required for Z_lP (dense upper bound; actual is sparser)', mem_Z_lP_GB, ' GB'
    2886              :          WRITE (bs_env%unit_nr, '(T4,A,F25.2,A)') &
    2887           10 :             'Required for χ, W, Σ panels (peak per panel step)', mem_panels_GB, ' GB'
    2888              :          WRITE (bs_env%unit_nr, '(T4,A,F17.2,A)') &
    2889           10 :             'Required for Z_lP solve peak (D_local+φ, worst-case atom)', mem_Zlp_peak_GB, ' GB'
    2890              :          WRITE (bs_env%unit_nr, '(T6,A,I21,A,F10.2,A)') &
    2891           10 :             'worst-case n_local_grid', max_n_local_grid, ' points  (D_local', mem_D_local_GB, ' GB)'
    2892           10 :          WRITE (bs_env%unit_nr, '(A)') ' '
    2893              : 
    2894           10 :          IF (mem_avail_GB > 0.0_dp .AND. mem_Z_lP_GB > mem_avail_GB) THEN
    2895              :             WRITE (bs_env%unit_nr, '(T2,A)') &
    2896            0 :                '*** WARNING: Estimated Z_lP memory exceeds available memory per process ***'
    2897              :             WRITE (bs_env%unit_nr, '(T4,A,F6.2,A,F6.2,A)') &
    2898            0 :                'Z_lP upper bound: ', mem_Z_lP_GB, ' GB  >  available: ', mem_avail_GB, ' GB'
    2899              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2900            0 :                'Z_lP (n_grid × n_RI) is distributed across all MPI ranks. To reduce per-rank'
    2901              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2902            0 :                'memory: add more nodes, use fewer MPI ranks per node, or increase'
    2903              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2904            0 :                'N_PROCS_PER_ATOM_Z_LP to distribute each atom block via ScaLAPACK (reduces'
    2905              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2906            0 :                'per-rank memory by ~1/G where G = N_PROCS_PER_ATOM_Z_LP).'
    2907            0 :             WRITE (bs_env%unit_nr, '(A)') ' '
    2908              :          END IF
    2909           10 :          IF (mem_avail_GB > 0.0_dp .AND. mem_panels_GB > mem_avail_GB) THEN
    2910              :             WRITE (bs_env%unit_nr, '(T2,A)') &
    2911            0 :                '*** WARNING: Estimated χ/W/Σ panel memory exceeds available memory per process ***'
    2912              :             WRITE (bs_env%unit_nr, '(T4,A,F6.2,A,F6.2,A)') &
    2913            0 :                'Panel peak estimate: ', mem_panels_GB, ' GB  >  available: ', mem_avail_GB, ' GB'
    2914              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2915            0 :                'Panel memory scales as ~3×panel_size×n_grid / n_procs. Options:'
    2916              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2917            0 :                '  - More nodes or fewer MPI ranks per node (increases n_procs, reduces share)'
    2918              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2919            0 :                '  - Increase N_PANELS (more, smaller panels → less peak memory per step)'
    2920            0 :             WRITE (bs_env%unit_nr, '(A)') ' '
    2921              :          END IF
    2922           10 :          IF (mem_avail_GB > 0.0_dp .AND. mem_Zlp_peak_GB > mem_avail_GB) THEN
    2923              :             WRITE (bs_env%unit_nr, '(T2,A)') &
    2924            0 :                '*** WARNING: Estimated Z_lP solve peak exceeds available memory per process ***'
    2925              :             WRITE (bs_env%unit_nr, '(T4,A,F8.2,A,F8.2,A)') &
    2926            0 :                'Z_lP solve peak: ', mem_Zlp_peak_GB, ' GB  >  available: ', mem_avail_GB, ' GB'
    2927              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2928            0 :                'The per-atom Gram matrix D_local(n_local_grid, n_local_grid) dominates and scales'
    2929              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2930            0 :                'as n_local_grid^2. It is NOT balanced across ranks (the rank owning the atom with'
    2931              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2932            0 :                'the largest integration sphere peaks well above the average). Options:'
    2933              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2934            0 :                '  - Increase N_PROCS_PER_ATOM_Z_LP=G to distribute D_local block-cyclic via'
    2935              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2936            0 :                '    ScaLAPACK (reduces the D_local term by ~1/G; no accuracy loss)'
    2937              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2938            0 :                '  - Reduce the RI-RS sphere cutoff (CUTOFF_RADIUS_RL_RI): D_local shrinks as'
    2939              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2940            0 :                '    n_local_grid^2, but this trades accuracy'
    2941              :             WRITE (bs_env%unit_nr, '(T4,A)') &
    2942            0 :                '  - Fewer MPI ranks per node (more memory per rank for the peak atom)'
    2943            0 :             WRITE (bs_env%unit_nr, '(A)') ' '
    2944              :          END IF
    2945              :       END IF
    2946              : 
    2947           20 :       CALL timestop(handle)
    2948              : 
    2949           60 :    END SUBROUTINE print_ri_rs_memory_estimate
    2950              : 
    2951              : ! **************************************************************************************************
    2952              : !> \brief Creates an empty (panel_chunks x neighborhood_chunks) DBCSR matrix with zero blocks
    2953              : !>        pre-allocated only where |centroid(panel_row r) - centroid(column c)| <= cutoff.
    2954              : !>        Used with retain_sparsity=.TRUE. in the subsequent dbcsr_multiply so distant blocks
    2955              : !>        of the grid-basis panels (φ G φ^T, Z W Z^T, ...) are never computed at all.
    2956              : !> \param L_pan ...
    2957              : !> \param L_full ...
    2958              : !> \param centroids ...
    2959              : !> \param cutoff ...
    2960              : !> \param blk0 ...
    2961              : !> \param A_template ...
    2962              : !> \param col_map ...
    2963              : ! **************************************************************************************************
    2964          124 :    SUBROUTINE build_geo_template_panel(L_pan, L_full, centroids, cutoff, blk0, A_template, col_map)
    2965              :       TYPE(dbcsr_type), INTENT(IN)                      :: L_pan, L_full
    2966              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)        :: centroids
    2967              :       REAL(KIND=dp), INTENT(IN)                         :: cutoff
    2968              :       INTEGER, INTENT(IN)                               :: blk0
    2969              :       TYPE(dbcsr_type), INTENT(OUT)                     :: A_template
    2970              :       INTEGER, DIMENSION(:), INTENT(IN), OPTIONAL       :: col_map
    2971              : 
    2972              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_geo_template_panel'
    2973              : 
    2974              :       INTEGER                                           :: c, cg, cs, handle, my_pcol, my_prow, &
    2975              :                                                            n_grid_blks, n_pan_blks, npcols, &
    2976              :                                                            nprows, r, rs
    2977          124 :       INTEGER, DIMENSION(:), POINTER                    :: grid_blk_sizes, pan_blk_sizes
    2978              :       REAL(KIND=dp)                                     :: cutoff2
    2979          124 :       REAL(KIND=dp), ALLOCATABLE                        :: zero_blk(:, :)
    2980              :       TYPE(dbcsr_distribution_type)                     :: dist
    2981              : 
    2982          124 :       CALL timeset(routineN, handle)
    2983              : 
    2984          124 :       cutoff2 = cutoff**2
    2985          124 :       CALL dbcsr_get_info(L_pan, nblkrows_total=n_pan_blks, row_blk_size=pan_blk_sizes)
    2986          124 :       CALL dbcsr_get_info(L_full, nblkrows_total=n_grid_blks, row_blk_size=grid_blk_sizes)
    2987              : 
    2988              :       ! create_product_matrix assigns row r to process MOD(r-1,nprows) and
    2989              :       ! col c to MOD(c-1,npcols), so we can determine local ownership analytically.
    2990          124 :       CALL create_product_matrix(L_pan, L_full, 'N', 'T', A_template)
    2991          124 :       CALL dbcsr_get_info(A_template, distribution=dist)
    2992              :       CALL dbcsr_distribution_get(dist, nprows=nprows, npcols=npcols, &
    2993          124 :                                   myprow=my_prow, mypcol=my_pcol)
    2994              : 
    2995              :       ALLOCATE (zero_blk(MAXVAL(pan_blk_sizes(1:n_pan_blks)), &
    2996         4402 :                          MAXVAL(grid_blk_sizes(1:n_grid_blks))))
    2997          124 :       zero_blk(:, :) = 0.0_dp
    2998              : 
    2999         1426 :       DO r = 1, n_pan_blks
    3000         1302 :          IF (MOD(r - 1, nprows) /= my_prow) CYCLE
    3001          651 :          rs = pan_blk_sizes(r)
    3002        14446 :          DO c = 1, n_grid_blks
    3003        13671 :             IF (MOD(c - 1, npcols) /= my_pcol) CYCLE
    3004        13671 :             cg = c
    3005        13671 :             IF (PRESENT(col_map)) cg = col_map(c)
    3006        13671 :             IF ((centroids(1, blk0 + r - 1) - centroids(1, cg))**2 + &
    3007              :                 (centroids(2, blk0 + r - 1) - centroids(2, cg))**2 + &
    3008         1302 :                 (centroids(3, blk0 + r - 1) - centroids(3, cg))**2 <= cutoff2) THEN
    3009        12741 :                cs = grid_blk_sizes(c)
    3010        12741 :                CALL dbcsr_put_block(A_template, r, c, zero_blk(1:rs, 1:cs))
    3011              :             END IF
    3012              :          END DO
    3013              :       END DO
    3014          124 :       CALL dbcsr_finalize(A_template)
    3015              : 
    3016          124 :       DEALLOCATE (zero_blk)
    3017          124 :       CALL timestop(handle)
    3018              : 
    3019          124 :    END SUBROUTINE build_geo_template_panel
    3020              : 
    3021              : ! **************************************************************************************************
    3022              : !> \brief Slices a contiguous range of grid row-blocks [blk0, blk1] out of a (grid x n) DBCSR
    3023              : !>        matrix into a new (P x n) panel matrix: iterate the source's local blocks, put the
    3024              : !>        in-range ones into the panel with a remapped row-block index, then finalize. Row-block
    3025              : !>        index i of the panel corresponds to source row-block blk0+i-1.
    3026              : !> \param mat_full ...
    3027              : !> \param blk0 ...
    3028              : !> \param blk1 ...
    3029              : !> \param mat_panel ...
    3030              : ! **************************************************************************************************
    3031         1532 :    SUBROUTINE extract_grid_panel(mat_full, blk0, blk1, mat_panel)
    3032              : 
    3033              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_full
    3034              :       INTEGER, INTENT(IN)                                :: blk0, blk1
    3035              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_panel
    3036              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'extract_grid_panel'
    3037              : 
    3038              :       INTEGER                                            :: ib, jb, npb
    3039         1532 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk_full, col_dist_full, &
    3040         1532 :                                                             row_blk_full, row_blk_pan, &
    3041         1532 :                                                             row_dist_full, row_dist_pan
    3042         1532 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk
    3043              :       TYPE(dbcsr_distribution_type)                      :: dist_full, dist_pan
    3044              :       TYPE(dbcsr_iterator_type)                          :: iter
    3045              :       INTEGER                                            :: handle
    3046              : 
    3047         1532 :       CALL timeset(routineN, handle)
    3048              : 
    3049              :       CALL dbcsr_get_info(mat_full, distribution=dist_full, &
    3050         1532 :                           row_blk_size=row_blk_full, col_blk_size=col_blk_full)
    3051         1532 :       CALL dbcsr_distribution_get(dist_full, row_dist=row_dist_full, col_dist=col_dist_full)
    3052              : 
    3053         1532 :       npb = blk1 - blk0 + 1
    3054         6128 :       ALLOCATE (row_dist_pan(npb), row_blk_pan(npb))
    3055        47488 :       row_dist_pan(:) = row_dist_full(blk0:blk1)
    3056        47488 :       row_blk_pan(:) = row_blk_full(blk0:blk1)
    3057              : 
    3058              :       CALL dbcsr_distribution_new(dist_pan, template=dist_full, &
    3059         1532 :                                   row_dist=row_dist_pan, col_dist=col_dist_full)
    3060              :       CALL dbcsr_create(mat_panel, name="grid_panel", dist=dist_pan, &
    3061              :                         matrix_type=dbcsr_type_no_symmetry, &
    3062         1532 :                         row_blk_size=row_blk_pan, col_blk_size=col_blk_full)
    3063              : 
    3064         1532 :       CALL dbcsr_iterator_start(iter, mat_full)
    3065        30212 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    3066        28680 :          CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
    3067        28680 :          IF (ib < blk0 .OR. ib > blk1) CYCLE
    3068        30212 :          CALL dbcsr_put_block(mat_panel, ib - blk0 + 1, jb, blk)
    3069              :       END DO
    3070         1532 :       CALL dbcsr_iterator_stop(iter)
    3071         1532 :       CALL dbcsr_finalize(mat_panel)
    3072              : 
    3073         1532 :       CALL dbcsr_distribution_release(dist_pan)
    3074         1532 :       DEALLOCATE (row_dist_pan, row_blk_pan)
    3075              : 
    3076         1532 :       CALL timestop(handle)
    3077              : 
    3078         4596 :    END SUBROUTINE extract_grid_panel
    3079              : 
    3080              : ! **************************************************************************************************
    3081              : !> \brief Marks which column blocks of a DBCSR matrix carry at least one non-zero block anywhere
    3082              : !>        (global union). Used to restrict the inner index of the panel multiplies to the
    3083              : !>        AO/RI atoms that actually touch the panel (exact: dropped rows only meet zeros).
    3084              : !> \param matrix ...
    3085              : !> \param para_env ...
    3086              : !> \param used ...
    3087              : ! **************************************************************************************************
    3088         1162 :    SUBROUTINE collect_used_col_blocks(matrix, para_env, used)
    3089              : 
    3090              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix
    3091              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    3092              :       LOGICAL, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: used
    3093              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'collect_used_col_blocks'
    3094              : 
    3095              :       INTEGER                                            :: ib, jb, nblkcols
    3096              :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: iused
    3097         1162 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk
    3098              :       TYPE(dbcsr_iterator_type)                          :: iter
    3099              :       INTEGER                                            :: handle
    3100              : 
    3101         1162 :       CALL timeset(routineN, handle)
    3102              : 
    3103         1162 :       CALL dbcsr_get_info(matrix, nblkcols_total=nblkcols)
    3104         3486 :       ALLOCATE (iused(nblkcols))
    3105         1162 :       iused(:) = 0
    3106              : 
    3107         1162 :       CALL dbcsr_iterator_start(iter, matrix)
    3108        21315 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    3109        20153 :          CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
    3110        20153 :          iused(jb) = 1
    3111              :       END DO
    3112         1162 :       CALL dbcsr_iterator_stop(iter)
    3113              : 
    3114         1162 :       CALL para_env%sum(iused)
    3115              : 
    3116         2324 :       ALLOCATE (used(nblkcols))
    3117         3900 :       used(:) = (iused(:) > 0)
    3118         1162 :       DEALLOCATE (iused)
    3119              : 
    3120         1162 :       CALL timestop(handle)
    3121              : 
    3122         2324 :    END SUBROUTINE collect_used_col_blocks
    3123              : 
    3124              : ! **************************************************************************************************
    3125              : !> \brief Copies the flagged block rows (compress_rows=.TRUE.) or block columns (.FALSE.) of a
    3126              : !>        DBCSR matrix into a compressed matrix. The subset keeps the parent's process assignment
    3127              : !>        along the compressed dimension, so every block stays on its owning rank: the extraction
    3128              : !>        is purely local (zero communication), like extract_grid_panel.
    3129              : !> \param mat_full ...
    3130              : !> \param used ...
    3131              : !> \param mat_out ...
    3132              : !> \param compress_rows ...
    3133              : !> \param blk_map ...
    3134              : ! **************************************************************************************************
    3135         3232 :    SUBROUTINE extract_masked_blocks(mat_full, used, mat_out, compress_rows, blk_map)
    3136              : 
    3137              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_full
    3138              :       LOGICAL, DIMENSION(:), INTENT(IN)                  :: used
    3139              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_out
    3140              :       LOGICAL, INTENT(IN)                                :: compress_rows
    3141              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT), &
    3142              :          OPTIONAL                                        :: blk_map
    3143              : 
    3144              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'extract_masked_blocks'
    3145              : 
    3146              :       INTEGER                                            :: ib, jb, n_blk, n_sub, r
    3147         3232 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: inv_map
    3148         3232 :       INTEGER, DIMENSION(:), POINTER                     :: blk_full, blk_sub, col_blk_full, &
    3149         3232 :                                                             col_dist_full, dist_full_1d, &
    3150         3232 :                                                             dist_sub_1d, row_blk_full, &
    3151         3232 :                                                             row_dist_full
    3152         3232 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk
    3153              :       TYPE(dbcsr_distribution_type)                      :: dist_full, dist_sub
    3154              :       TYPE(dbcsr_iterator_type)                          :: iter
    3155              :       INTEGER                                            :: handle
    3156              : 
    3157         3232 :       CALL timeset(routineN, handle)
    3158              : 
    3159              :       CALL dbcsr_get_info(mat_full, distribution=dist_full, &
    3160         3232 :                           row_blk_size=row_blk_full, col_blk_size=col_blk_full)
    3161         3232 :       CALL dbcsr_distribution_get(dist_full, row_dist=row_dist_full, col_dist=col_dist_full)
    3162              : 
    3163         3232 :       IF (compress_rows) THEN
    3164         2070 :          blk_full => row_blk_full
    3165         2070 :          dist_full_1d => row_dist_full
    3166              :       ELSE
    3167         1162 :          blk_full => col_blk_full
    3168         1162 :          dist_full_1d => col_dist_full
    3169              :       END IF
    3170         3232 :       n_blk = SIZE(blk_full)
    3171              : 
    3172        13976 :       n_sub = COUNT(used)
    3173         3232 :       CPASSERT(n_sub > 0)
    3174        19392 :       ALLOCATE (inv_map(n_blk), blk_sub(n_sub), dist_sub_1d(n_sub))
    3175         3316 :       IF (PRESENT(blk_map)) ALLOCATE (blk_map(n_sub))
    3176         3232 :       inv_map(:) = 0
    3177         3232 :       r = 0
    3178        13976 :       DO ib = 1, n_blk
    3179        13976 :          IF (used(ib)) THEN
    3180        10744 :             r = r + 1
    3181        10744 :             inv_map(ib) = r
    3182        10744 :             blk_sub(r) = blk_full(ib)
    3183        10744 :             dist_sub_1d(r) = dist_full_1d(ib)
    3184        10744 :             IF (PRESENT(blk_map)) blk_map(r) = ib
    3185              :          END IF
    3186              :       END DO
    3187              : 
    3188         3232 :       IF (compress_rows) THEN
    3189              :          CALL dbcsr_distribution_new(dist_sub, template=dist_full, &
    3190         2070 :                                      row_dist=dist_sub_1d, col_dist=col_dist_full)
    3191              :          CALL dbcsr_create(mat_out, name="row_subset", dist=dist_sub, &
    3192              :                            matrix_type=dbcsr_type_no_symmetry, &
    3193         2070 :                            row_blk_size=blk_sub, col_blk_size=col_blk_full)
    3194              :       ELSE
    3195              :          CALL dbcsr_distribution_new(dist_sub, template=dist_full, &
    3196         1162 :                                      row_dist=row_dist_full, col_dist=dist_sub_1d)
    3197              :          CALL dbcsr_create(mat_out, name="col_subset", dist=dist_sub, &
    3198              :                            matrix_type=dbcsr_type_no_symmetry, &
    3199         1162 :                            row_blk_size=row_blk_full, col_blk_size=blk_sub)
    3200              :       END IF
    3201              : 
    3202         3232 :       CALL dbcsr_iterator_start(iter, mat_full)
    3203        33158 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    3204        29926 :          CALL dbcsr_iterator_next_block(iter, ib, jb, blk)
    3205        33158 :          IF (compress_rows) THEN
    3206         9773 :             IF (inv_map(ib) > 0) CALL dbcsr_put_block(mat_out, inv_map(ib), jb, blk)
    3207              :          ELSE
    3208        20153 :             IF (inv_map(jb) > 0) CALL dbcsr_put_block(mat_out, ib, inv_map(jb), blk)
    3209              :          END IF
    3210              :       END DO
    3211         3232 :       CALL dbcsr_iterator_stop(iter)
    3212         3232 :       CALL dbcsr_finalize(mat_out)
    3213              : 
    3214         3232 :       CALL dbcsr_distribution_release(dist_sub)
    3215         3232 :       DEALLOCATE (inv_map, blk_sub, dist_sub_1d)
    3216              : 
    3217         3232 :       CALL timestop(handle)
    3218              : 
    3219         6464 :    END SUBROUTINE extract_masked_blocks
    3220              : 
    3221              : ! **************************************************************************************************
    3222              : !> \brief Pre-seeds a square per-atom-blocked DBCSR matrix (G_munu, D_munu, V_PQ, W_PQ) with zero
    3223              : !>        blocks only for atom pairs within radius, for use with copy_fm_to_dbcsr(keep_sparsity=T):
    3224              : !>        the CUTOFF_RADIUS_G_W operator truncation.
    3225              : !> \param matrix ...
    3226              : !> \param centers ...
    3227              : !> \param radius ...
    3228              : ! **************************************************************************************************
    3229            0 :    SUBROUTINE reserve_blocks_within_radius(matrix, centers, radius)
    3230              : 
    3231              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix
    3232              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: centers
    3233              :       REAL(KIND=dp), INTENT(IN)                          :: radius
    3234              : 
    3235              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'reserve_blocks_within_radius'
    3236              : 
    3237              :       INTEGER                                            :: handle, i, j, my_pcol, my_prow, &
    3238              :                                                             nblkcols, nblkrows
    3239            0 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk, col_dist, row_blk, row_dist
    3240              :       REAL(KIND=dp)                                      :: radius2
    3241            0 :       REAL(KIND=dp), ALLOCATABLE                         :: zero_blk(:, :)
    3242              :       TYPE(dbcsr_distribution_type)                      :: dist
    3243              : 
    3244            0 :       CALL timeset(routineN, handle)
    3245              : 
    3246              :       CALL dbcsr_get_info(matrix, nblkrows_total=nblkrows, nblkcols_total=nblkcols, &
    3247            0 :                           row_blk_size=row_blk, col_blk_size=col_blk, distribution=dist)
    3248              :       CALL dbcsr_distribution_get(dist, row_dist=row_dist, col_dist=col_dist, &
    3249            0 :                                   myprow=my_prow, mypcol=my_pcol)
    3250            0 :       CPASSERT(nblkrows == SIZE(centers, 2))
    3251            0 :       CPASSERT(nblkcols == SIZE(centers, 2))
    3252              : 
    3253            0 :       radius2 = radius**2
    3254            0 :       ALLOCATE (zero_blk(MAXVAL(row_blk(1:nblkrows)), MAXVAL(col_blk(1:nblkcols))))
    3255            0 :       zero_blk(:, :) = 0.0_dp
    3256              : 
    3257            0 :       DO i = 1, nblkrows
    3258            0 :          IF (row_dist(i) /= my_prow) CYCLE
    3259            0 :          DO j = 1, nblkcols
    3260            0 :             IF (col_dist(j) /= my_pcol) CYCLE
    3261            0 :             IF ((centers(1, i) - centers(1, j))**2 + (centers(2, i) - centers(2, j))**2 + &
    3262            0 :                 (centers(3, i) - centers(3, j))**2 <= radius2) THEN
    3263            0 :                CALL dbcsr_put_block(matrix, i, j, zero_blk(1:row_blk(i), 1:col_blk(j)))
    3264              :             END IF
    3265              :          END DO
    3266              :       END DO
    3267            0 :       CALL dbcsr_finalize(matrix)
    3268              : 
    3269            0 :       DEALLOCATE (zero_blk)
    3270            0 :       CALL timestop(handle)
    3271              : 
    3272            0 :    END SUBROUTINE reserve_blocks_within_radius
    3273              : 
    3274              : ! **************************************************************************************************
    3275              : !> \brief Creates the (empty) result matrix of op(mat_left) * op(mat_right) with the correct block
    3276              : !>        structure and a distribution on the shared process grid, ready to be filled by
    3277              : !>        dbcsr_multiply. Row structure comes from op(left), column structure from op(right).
    3278              : !> \param mat_left ...
    3279              : !> \param mat_right ...
    3280              : !> \param transa 'N' or 'T' applied to mat_left
    3281              : !> \param transb 'N' or 'T' applied to mat_right
    3282              : !> \param mat_out ...
    3283              : ! **************************************************************************************************
    3284         4174 :    SUBROUTINE create_product_matrix(mat_left, mat_right, transa, transb, mat_out)
    3285              : 
    3286              :       TYPE(dbcsr_type), INTENT(IN)                       :: mat_left, mat_right
    3287              :       CHARACTER(LEN=1), INTENT(IN)                       :: transa, transb
    3288              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_out
    3289              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'create_product_matrix'
    3290              : 
    3291              :       INTEGER                                            :: i, npcols, nprows
    3292         4174 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk_l, col_blk_r, out_col_blk, &
    3293         4174 :                                                             out_col_dist, out_row_blk, out_row_dist, &
    3294         4174 :                                                             row_blk_l, row_blk_r
    3295              :       TYPE(dbcsr_distribution_type)                      :: dist_l, dist_out
    3296              :       INTEGER                                            :: handle
    3297              : 
    3298         4174 :       CALL timeset(routineN, handle)
    3299              : 
    3300         4174 :       CALL dbcsr_get_info(mat_left, distribution=dist_l, row_blk_size=row_blk_l, col_blk_size=col_blk_l)
    3301         4174 :       CALL dbcsr_get_info(mat_right, row_blk_size=row_blk_r, col_blk_size=col_blk_r)
    3302         4174 :       CALL dbcsr_distribution_get(dist_l, nprows=nprows, npcols=npcols)
    3303              : 
    3304              :       ! block SIZES follow op(left)/op(right); DISTRIBUTIONS are freshly round-robined onto the
    3305              :       ! shared process grid (a transposed operand's row-dist is NOT a valid col-dist on a
    3306              :       ! non-square grid). dbcsr_multiply redistributes internally, so any valid mapping works.
    3307         4174 :       IF (transa == 'N') THEN
    3308         4174 :          out_row_blk => row_blk_l
    3309              :       ELSE
    3310            0 :          out_row_blk => col_blk_l
    3311              :       END IF
    3312         4174 :       IF (transb == 'N') THEN
    3313         3038 :          out_col_blk => col_blk_r
    3314              :       ELSE
    3315         1136 :          out_col_blk => row_blk_r
    3316              :       END IF
    3317              : 
    3318        20870 :       ALLOCATE (out_row_dist(SIZE(out_row_blk)), out_col_dist(SIZE(out_col_blk)))
    3319        64648 :       DO i = 1, SIZE(out_row_blk)
    3320        64648 :          out_row_dist(i) = MOD(i - 1, nprows)
    3321              :       END DO
    3322        29084 :       DO i = 1, SIZE(out_col_blk)
    3323        29084 :          out_col_dist(i) = MOD(i - 1, npcols)
    3324              :       END DO
    3325              : 
    3326              :       CALL dbcsr_distribution_new(dist_out, template=dist_l, &
    3327         4174 :                                   row_dist=out_row_dist, col_dist=out_col_dist)
    3328              :       CALL dbcsr_create(mat_out, name="panel_product", dist=dist_out, &
    3329              :                         matrix_type=dbcsr_type_no_symmetry, &
    3330         4174 :                         row_blk_size=out_row_blk, col_blk_size=out_col_blk)
    3331         4174 :       CALL dbcsr_distribution_release(dist_out)
    3332         4174 :       DEALLOCATE (out_row_dist, out_col_dist)
    3333              : 
    3334         4174 :       CALL timestop(handle)
    3335              : 
    3336        12522 :    END SUBROUTINE create_product_matrix
    3337              : 
    3338              : ! **************************************************************************************************
    3339              : !> \brief Builds the AO-space Green's function operator G^occ/vir_µν (AO x AO DBCSR)
    3340              : !> \param bs_env ...
    3341              : !> \param tau ...
    3342              : !> \param ispin ...
    3343              : !> \param occ ...
    3344              : !> \param vir ...
    3345              : !> \param template ...
    3346              : !> \param matrix_G_ao ...
    3347              : ! **************************************************************************************************
    3348         2848 :    SUBROUTINE build_G_ao(bs_env, tau, ispin, occ, vir, template, matrix_G_ao)
    3349              : 
    3350              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    3351              :       REAL(KIND=dp), INTENT(IN)                          :: tau
    3352              :       INTEGER, INTENT(IN)                                :: ispin
    3353              :       LOGICAL, INTENT(IN)                                :: occ, vir
    3354              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: template
    3355              :       TYPE(dbcsr_type), INTENT(OUT)                      :: matrix_G_ao
    3356              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_G_ao'
    3357              : 
    3358         1424 :       INTEGER, DIMENSION(:), POINTER                     :: blk_ao, dist_row_ao
    3359              :       TYPE(cp_fm_type), POINTER                          :: fm_G
    3360              :       TYPE(dbcsr_distribution_type)                      :: dist_ao_ao
    3361              :       INTEGER                                            :: handle
    3362              : 
    3363         1424 :       CALL timeset(routineN, handle)
    3364              : 
    3365         1424 :       IF (occ) THEN
    3366          724 :          fm_G => bs_env%fm_Gocc
    3367              :       ELSE
    3368          700 :          fm_G => bs_env%fm_Gvir
    3369              :       END IF
    3370              : 
    3371         1424 :       CALL G_occ_vir(bs_env, tau, fm_G, ispin, occ=occ, vir=vir)
    3372              : 
    3373         1424 :       CALL setup_square_topology(template, dist_ao_ao, blk_ao, dist_row_ao)
    3374              :       CALL dbcsr_create(matrix_G_ao, name="G_ao", dist=dist_ao_ao, &
    3375              :                         matrix_type=dbcsr_type_no_symmetry, &
    3376         1424 :                         row_blk_size=blk_ao, col_blk_size=blk_ao)
    3377              : 
    3378              :       ! Optional CUTOFF_RADIUS_G_W operator truncation: only atom-pair blocks within the radius
    3379              :       ! are reserved and filled.
    3380         1424 :       IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
    3381              :           ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
    3382              :          CALL reserve_blocks_within_radius(matrix_G_ao, bs_env%ri_rs%atom_centers, &
    3383            0 :                                            bs_env%ri_rs%cutoff_radius_g_w)
    3384            0 :          CALL copy_fm_to_dbcsr(fm_G, matrix_G_ao, keep_sparsity=.TRUE.)
    3385              :       ELSE
    3386         1424 :          CALL copy_fm_to_dbcsr(fm_G, matrix_G_ao, keep_sparsity=.FALSE.)
    3387              :       END IF
    3388         1424 :       CALL dbcsr_filter(matrix_G_ao, bs_env%eps_filter)
    3389              : 
    3390              :       ! release only the topology; keep matrix_G_ao for the caller
    3391         1424 :       CALL release_square_topology(dist=dist_ao_ao, mapped_dist=dist_row_ao)
    3392              : 
    3393         1424 :       CALL timestop(handle)
    3394              : 
    3395         1424 :    END SUBROUTINE build_G_ao
    3396              : 
    3397              : ! **************************************************************************************************
    3398              : !> \brief Panel-streaming evaluation of  out += scale * L_out^T (A_grid ∘ B_grid) L_out,
    3399              : !>        where A_grid = L_A M_A L_A^T and B_grid = L_B M_B L_B^T, WITHOUT ever forming the full
    3400              : !>        grid x grid objects. The grid (row) index is processed in panels of ~panel_size rows; for
    3401              : !>        each panel only P x grid slabs are built, Hadamard-multiplied, and contracted into the
    3402              : !>        (small) output. Algebraically identical to L_out^T (A_grid ∘ B_grid) L_out summed over
    3403              : !>        grid rows, so the result matches the non-streamed path to eps_filter.
    3404              : !>
    3405              : !>        Mapping (L in {phi (grid x AO), Z (grid x RI)}, M the AO/RI-space operator):
    3406              : !>          chi :   L_A=L_B=phi, M_A=G_occ_ao, M_B=G_vir_ao, L_out=Z   -> RI x RI
    3407              : !>          Sig :   L_A=phi (M_A=D/G), L_B=Z (M_B=V/W),       L_out=phi -> AO x AO
    3408              : !> \param L_A ...
    3409              : !> \param M_A ...
    3410              : !> \param L_B ...
    3411              : !> \param M_B ...
    3412              : !> \param L_out ...
    3413              : !> \param mat_out ...
    3414              : !> \param scale ...
    3415              : !> \param eps ...
    3416              : !> \param para_env ...
    3417              : !> \param pan_first ...
    3418              : !> \param pan_last ...
    3419              : !> \param lb_eq_la ...
    3420              : !> \param lout_eq_la ...
    3421              : !> \param zero_out ...
    3422              : !> \param keep_sparsity ...
    3423              : !> \param centroids ...
    3424              : !> \param cutoff ...
    3425              : !> \param grid_occupation ...
    3426              : ! **************************************************************************************************
    3427          374 :    SUBROUTINE contract_grid_panels(L_A, M_A, L_B, M_B, L_out, mat_out, scale, eps, para_env, &
    3428          374 :                                    pan_first, pan_last, lb_eq_la, lout_eq_la, zero_out, &
    3429          374 :                                    keep_sparsity, centroids, cutoff, grid_occupation)
    3430              : 
    3431              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: L_A
    3432              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: M_A
    3433              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: L_B
    3434              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: M_B
    3435              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: L_out
    3436              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_out
    3437              :       REAL(KIND=dp), INTENT(IN)                          :: scale, eps
    3438              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    3439              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: pan_first, pan_last
    3440              :       LOGICAL, INTENT(IN)                                :: lb_eq_la, lout_eq_la, zero_out
    3441              :       LOGICAL, INTENT(IN), OPTIONAL                      :: keep_sparsity
    3442              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), OPTIONAL :: centroids
    3443              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: cutoff
    3444              :       REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: grid_occupation
    3445              : 
    3446              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'contract_grid_panels'
    3447              : 
    3448              :       INTEGER                                            :: blk0, blk1, handle, ipan, &
    3449              :                                                             n_grid_total, ncols_pan, nrows_pan
    3450          374 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: gmap
    3451              :       LOGICAL                                            :: my_keep_sparsity, use_cutoff
    3452          374 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: grid_used, usedA, usedB
    3453              :       TYPE(dbcsr_type)                                   :: A_pan, B_pan, C_pan, LA_pan, LA_panC, &
    3454              :                                                             LB_pan, LB_panC, Lout_pan, MA_sub, &
    3455              :                                                             MB_sub, tmp2, tmpA, tmpB
    3456              :       TYPE(dbcsr_type), POINTER                          :: RB_A, RB_B, RB_out
    3457              :       TYPE(dbcsr_type), TARGET                           :: LA_near, LB_near, Lout_near
    3458              : 
    3459          374 :       CALL timeset(routineN, handle)
    3460              : 
    3461          374 :       my_keep_sparsity = .FALSE.
    3462          374 :       IF (PRESENT(keep_sparsity)) my_keep_sparsity = keep_sparsity
    3463          374 :       use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
    3464          374 :       IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
    3465          374 :       IF (PRESENT(grid_occupation)) grid_occupation = 0.0_dp
    3466              : 
    3467          374 :       CALL dbcsr_get_info(L_A, nfullrows_total=n_grid_total)
    3468              : 
    3469          374 :       IF (zero_out) CALL dbcsr_set(mat_out, 0.0_dp)
    3470              : 
    3471          770 :       DO ipan = 1, SIZE(pan_first)
    3472          396 :          blk0 = pan_first(ipan)
    3473          396 :          blk1 = pan_last(ipan)
    3474              : 
    3475              :          ! phi/Z panel slices (P x n)
    3476          396 :          CALL extract_grid_panel(L_A, blk0, blk1, LA_pan)
    3477          396 :          IF (.NOT. lb_eq_la) CALL extract_grid_panel(L_B, blk0, blk1, LB_pan)
    3478          396 :          IF (.NOT. lout_eq_la) CALL extract_grid_panel(L_out, blk0, blk1, Lout_pan)
    3479              : 
    3480              :          ! Which AO/RI atoms (column blocks) actually touch this panel: the inner index of
    3481              :          ! every multiply below is restricted to them, so only the matching rows of the
    3482              :          ! system-wide operators M_A/M_B ever enter Cannon (exact: dropped rows meet zeros).
    3483          396 :          CALL collect_used_col_blocks(LA_pan, para_env, usedA)
    3484          396 :          IF (.NOT. lb_eq_la) THEN
    3485           26 :             CALL collect_used_col_blocks(LB_pan, para_env, usedB)
    3486              :          ELSE
    3487          370 :             IF (ALLOCATED(usedB)) DEALLOCATE (usedB)
    3488         1980 :             ALLOCATE (usedB, SOURCE=usedA)
    3489              :          END IF
    3490          396 :          IF (.NOT. (ANY(usedA) .AND. ANY(usedB))) THEN
    3491              :             ! empty panel slice: its Hadamard contribution is exactly zero on all ranks
    3492            0 :             CALL dbcsr_release(LA_pan)
    3493            0 :             IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_pan)
    3494            0 :             IF (.NOT. lout_eq_la) CALL dbcsr_release(Lout_pan)
    3495              :             CYCLE
    3496              :          END IF
    3497              : 
    3498              :          ! Grid rows within reach of the panel: with the CUTOFF_RADIUS_RL_W truncation only
    3499              :          ! they can appear as columns of the panel products / inner rows of the L_out multiply,
    3500              :          ! so the system-wide phi/Z right operands are cut down to this neighborhood slice
    3501              :          ! (local extraction, zero communication; exact w.r.t. the geo template).
    3502          396 :          IF (use_cutoff) THEN
    3503           44 :             CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, grid_used)
    3504           44 :             CALL extract_masked_blocks(L_A, grid_used, LA_near, compress_rows=.TRUE., blk_map=gmap)
    3505           44 :             IF (.NOT. lb_eq_la) CALL extract_masked_blocks(L_B, grid_used, LB_near, compress_rows=.TRUE.)
    3506           44 :             IF (.NOT. lout_eq_la) CALL extract_masked_blocks(L_out, grid_used, Lout_near, compress_rows=.TRUE.)
    3507              :             RB_A => LA_near
    3508              :          ELSE
    3509              :             RB_A => L_A
    3510              :          END IF
    3511          396 :          IF (lb_eq_la) THEN
    3512              :             RB_B => RB_A
    3513           26 :          ELSE IF (use_cutoff) THEN
    3514              :             RB_B => LB_near
    3515              :          ELSE
    3516           22 :             RB_B => L_B
    3517              :          END IF
    3518          396 :          IF (lout_eq_la) THEN
    3519              :             RB_out => RB_A
    3520          370 :          ELSE IF (use_cutoff) THEN
    3521              :             RB_out => Lout_near
    3522              :          ELSE
    3523          330 :             RB_out => L_out
    3524              :          END IF
    3525              : 
    3526              :          ! A_pan = LA_pan * M_A * L_A^T   (P x grid_near).
    3527              :          ! When cutoff is active, A_pan is pre-seeded with only nearby blocks via
    3528              :          ! build_geo_template_panel, and the multiply uses retain_sparsity to skip
    3529              :          ! computing distant blocks entirely (exact: they are zero by locality).
    3530          396 :          CALL extract_masked_blocks(LA_pan, usedA, LA_panC, compress_rows=.FALSE.)
    3531          396 :          CALL extract_masked_blocks(M_A, usedA, MA_sub, compress_rows=.TRUE.)
    3532          396 :          CALL create_product_matrix(LA_panC, MA_sub, 'N', 'N', tmpA)
    3533          396 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, LA_panC, MA_sub, 0.0_dp, tmpA, filter_eps=eps)
    3534          396 :          CALL dbcsr_release(MA_sub)
    3535          396 :          IF (use_cutoff) THEN
    3536              :             CALL build_geo_template_panel(LA_pan, LA_near, centroids, cutoff, blk0, A_pan, &
    3537           44 :                                           col_map=gmap)
    3538              :          ELSE
    3539          352 :             CALL create_product_matrix(tmpA, RB_A, 'N', 'T', A_pan)
    3540              :          END IF
    3541              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_A, 0.0_dp, A_pan, &
    3542          396 :                              filter_eps=eps, retain_sparsity=use_cutoff)
    3543          396 :          CALL dbcsr_release(tmpA)
    3544              : 
    3545              :          ! Grid-basis occupation of A_pan = φ G φ^T, accumulated over ALL panels into the
    3546              :          ! occupation of the (never formed) full grid x grid object:
    3547              :          !   sum_panels nnz(A_pan) / n_grid^2,  with nnz = occ * pan_rows * pan_cols.
    3548              :          ! Panel-independent by construction -- a single-panel sample would instead report the
    3549              :          ! local neighbor count of whichever region happens to land in that panel.
    3550          396 :          IF (PRESENT(grid_occupation)) THEN
    3551          370 :             CALL dbcsr_get_info(A_pan, nfullrows_total=nrows_pan, nfullcols_total=ncols_pan)
    3552              :             grid_occupation = grid_occupation + dbcsr_get_occupation(A_pan)* &
    3553              :                               REAL(ncols_pan, dp)*REAL(nrows_pan, dp)/ &
    3554          370 :                               (REAL(n_grid_total, dp)*REAL(n_grid_total, dp))
    3555              :          END IF
    3556              : 
    3557              :          ! B_pan = LB_pan * M_B * L_B^T   (P x grid_near); reuse the L_A slices when L_B == L_A.
    3558              :          ! With keep_sparsity, B_pan is pre-populated with A_pan's block structure so that
    3559              :          ! retain_sparsity forces the final multiply to fill only those blocks (exact for ∘).
    3560          396 :          IF (lb_eq_la) THEN
    3561          370 :             CALL extract_masked_blocks(M_B, usedA, MB_sub, compress_rows=.TRUE.)
    3562          370 :             CALL create_product_matrix(LA_panC, MB_sub, 'N', 'N', tmpB)
    3563          370 :             CALL dbcsr_multiply('N', 'N', 1.0_dp, LA_panC, MB_sub, 0.0_dp, tmpB, filter_eps=eps)
    3564              :          ELSE
    3565           26 :             CALL extract_masked_blocks(LB_pan, usedB, LB_panC, compress_rows=.FALSE.)
    3566           26 :             CALL extract_masked_blocks(M_B, usedB, MB_sub, compress_rows=.TRUE.)
    3567           26 :             CALL create_product_matrix(LB_panC, MB_sub, 'N', 'N', tmpB)
    3568           26 :             CALL dbcsr_multiply('N', 'N', 1.0_dp, LB_panC, MB_sub, 0.0_dp, tmpB, filter_eps=eps)
    3569           26 :             CALL dbcsr_release(LB_panC)
    3570              :          END IF
    3571          396 :          CALL dbcsr_release(MB_sub)
    3572          396 :          IF (my_keep_sparsity) THEN
    3573          396 :             CALL dbcsr_create(B_pan, template=A_pan)
    3574          396 :             CALL dbcsr_copy(B_pan, A_pan)
    3575          396 :             CALL dbcsr_set(B_pan, 0.0_dp)
    3576              :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_B, 0.0_dp, B_pan, &
    3577          396 :                                 filter_eps=eps, retain_sparsity=.TRUE.)
    3578              :          ELSE
    3579            0 :             CALL create_product_matrix(tmpB, RB_B, 'N', 'T', B_pan)
    3580            0 :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_B, 0.0_dp, B_pan, filter_eps=eps)
    3581              :          END IF
    3582          396 :          CALL dbcsr_release(tmpB)
    3583          396 :          CALL dbcsr_release(LA_panC)
    3584              : 
    3585              :          ! C_pan = scale * (A_pan ∘ B_pan)   (P x grid_near)
    3586          396 :          CALL dbcsr_create(C_pan, template=A_pan)
    3587          396 :          CALL hadamard_product(A_pan, B_pan, C_pan, scale)
    3588          396 :          CALL dbcsr_release(A_pan)
    3589          396 :          CALL dbcsr_release(B_pan)
    3590              : 
    3591              :          ! tmp2 = C_pan * L_out   (P x n_out; inner index restricted to the neighborhood)
    3592          396 :          CALL create_product_matrix(C_pan, RB_out, 'N', 'N', tmp2)
    3593          396 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_out, 0.0_dp, tmp2, filter_eps=eps)
    3594          396 :          CALL dbcsr_release(C_pan)
    3595              : 
    3596              :          ! mat_out += L_out_pan^T * tmp2   (accumulate: beta = 1)
    3597          396 :          IF (lout_eq_la) THEN
    3598           26 :             CALL dbcsr_multiply('T', 'N', 1.0_dp, LA_pan, tmp2, 1.0_dp, mat_out, filter_eps=eps)
    3599              :          ELSE
    3600          370 :             CALL dbcsr_multiply('T', 'N', 1.0_dp, Lout_pan, tmp2, 1.0_dp, mat_out, filter_eps=eps)
    3601          370 :             CALL dbcsr_release(Lout_pan)
    3602              :          END IF
    3603          396 :          CALL dbcsr_release(tmp2)
    3604          396 :          IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_pan)
    3605          396 :          CALL dbcsr_release(LA_pan)
    3606         1958 :          IF (use_cutoff) THEN
    3607           44 :             CALL dbcsr_release(LA_near)
    3608           44 :             IF (.NOT. lb_eq_la) CALL dbcsr_release(LB_near)
    3609           44 :             IF (.NOT. lout_eq_la) CALL dbcsr_release(Lout_near)
    3610              :          END IF
    3611              : 
    3612              :       END DO
    3613              : 
    3614          374 :       CALL timestop(handle)
    3615              : 
    3616          748 :    END SUBROUTINE contract_grid_panels
    3617              : 
    3618              : ! **************************************************************************************************
    3619              : !> \brief Σ^c-specific panel loop: computes both the occupied (neg) and virtual (pos) contributions
    3620              : !>        in a single pass over grid panels, forming W_pan = Z_panel × W_aux × Z^T only ONCE per
    3621              : !>        panel and reusing it for both the G^occ and G^vir Hadamard contractions.
    3622              : !>
    3623              : !>        Computes:
    3624              : !>          mat_Sigma_neg = φ^T ( (φ G^occ φ^T) ∘ (Z W^MIC Z^T) ) φ
    3625              : !>          mat_Sigma_pos = φ^T ( (φ G^vir φ^T) ∘ (Z W^MIC Z^T) ) φ
    3626              : !>
    3627              : !> \param mat_phi ...
    3628              : !> \param mat_Z ...
    3629              : !> \param mat_G_occ_ao ...
    3630              : !> \param mat_G_vir_ao ...
    3631              : !> \param mat_W_aux ...
    3632              : !> \param mat_Sigma_neg ...
    3633              : !> \param mat_Sigma_pos ...
    3634              : !> \param eps ...
    3635              : !> \param para_env ...
    3636              : !> \param pan_first ...
    3637              : !> \param pan_last ...
    3638              : !> \param keep_sparsity ...
    3639              : !> \param centroids ...
    3640              : !> \param cutoff ...
    3641              : ! **************************************************************************************************
    3642          350 :    SUBROUTINE contract_grid_panels_sigma_c(mat_phi, mat_Z, mat_G_occ_ao, mat_G_vir_ao, &
    3643              :                                            mat_W_aux, mat_Sigma_neg, mat_Sigma_pos, eps, para_env, &
    3644          350 :                                            pan_first, pan_last, keep_sparsity, centroids, cutoff)
    3645              : 
    3646              :       TYPE(dbcsr_type), INTENT(INOUT), TARGET            :: mat_phi, mat_Z
    3647              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_G_occ_ao, mat_G_vir_ao, mat_W_aux
    3648              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_Sigma_neg, mat_Sigma_pos
    3649              :       REAL(KIND=dp), INTENT(IN)                          :: eps
    3650              :       TYPE(mp_para_env_type), INTENT(IN), POINTER        :: para_env
    3651              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: pan_first, pan_last
    3652              :       LOGICAL, INTENT(IN), OPTIONAL                      :: keep_sparsity
    3653              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN), OPTIONAL :: centroids
    3654              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: cutoff
    3655              : 
    3656              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'contract_grid_panels_sigma_c'
    3657              : 
    3658              :       INTEGER                                            :: blk0, blk1, handle, ipan
    3659          350 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: gmap
    3660              :       LOGICAL                                            :: my_keep_sparsity, use_cutoff
    3661          350 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: grid_used, used_ao, used_ri
    3662              :       TYPE(dbcsr_type)                                   :: A_occ, A_vir, C_pan, G_occ_sub, &
    3663              :                                                             G_vir_sub, phi_pan, phi_panC, tmp2, &
    3664              :                                                             tmpA, tmpB, W_pan, W_sub, Z_pan, &
    3665              :                                                             Z_panC
    3666              :       TYPE(dbcsr_type), POINTER                          :: RB_phi, RB_Z
    3667              :       TYPE(dbcsr_type), TARGET                           :: phi_near, Z_near
    3668              : 
    3669          350 :       CALL timeset(routineN, handle)
    3670              : 
    3671          350 :       my_keep_sparsity = .FALSE.
    3672          350 :       IF (PRESENT(keep_sparsity)) my_keep_sparsity = keep_sparsity
    3673          350 :       use_cutoff = PRESENT(centroids) .AND. PRESENT(cutoff)
    3674          350 :       IF (use_cutoff) use_cutoff = cutoff > 0.0_dp
    3675              : 
    3676          350 :       CALL dbcsr_set(mat_Sigma_neg, 0.0_dp)
    3677          350 :       CALL dbcsr_set(mat_Sigma_pos, 0.0_dp)
    3678              : 
    3679          720 :       DO ipan = 1, SIZE(pan_first)
    3680          370 :          blk0 = pan_first(ipan)
    3681          370 :          blk1 = pan_last(ipan)
    3682              : 
    3683          370 :          CALL extract_grid_panel(mat_phi, blk0, blk1, phi_pan)
    3684          370 :          CALL extract_grid_panel(mat_Z, blk0, blk1, Z_pan)
    3685              : 
    3686              :          ! AO/RI atoms touching this panel: only the matching rows of G_occ/G_vir/W ever
    3687              :          ! enter the multiplies below (exact: dropped rows meet zero columns of the panel).
    3688          370 :          CALL collect_used_col_blocks(phi_pan, para_env, used_ao)
    3689          370 :          CALL collect_used_col_blocks(Z_pan, para_env, used_ri)
    3690          370 :          IF (.NOT. (ANY(used_ao) .AND. ANY(used_ri))) THEN
    3691            0 :             CALL dbcsr_release(phi_pan)
    3692            0 :             CALL dbcsr_release(Z_pan)
    3693            0 :             CYCLE
    3694              :          END IF
    3695              : 
    3696              :          ! Neighborhood slices of phi/Z (grid rows within cutoff of the panel): they replace
    3697              :          ! the system-wide right operands in every multiply (local extraction, zero comm).
    3698          370 :          IF (use_cutoff) THEN
    3699           40 :             CALL mask_grid_blocks_near_panel(centroids, blk0, blk1, cutoff, grid_used)
    3700           40 :             CALL extract_masked_blocks(mat_phi, grid_used, phi_near, compress_rows=.TRUE., blk_map=gmap)
    3701           40 :             CALL extract_masked_blocks(mat_Z, grid_used, Z_near, compress_rows=.TRUE.)
    3702           40 :             RB_phi => phi_near
    3703           40 :             RB_Z => Z_near
    3704              :          ELSE
    3705              :             RB_phi => mat_phi
    3706              :             RB_Z => mat_Z
    3707              :          END IF
    3708              : 
    3709          370 :          CALL extract_masked_blocks(phi_pan, used_ao, phi_panC, compress_rows=.FALSE.)
    3710          370 :          CALL extract_masked_blocks(Z_pan, used_ri, Z_panC, compress_rows=.FALSE.)
    3711          370 :          CALL extract_masked_blocks(mat_G_occ_ao, used_ao, G_occ_sub, compress_rows=.TRUE.)
    3712          370 :          CALL extract_masked_blocks(mat_G_vir_ao, used_ao, G_vir_sub, compress_rows=.TRUE.)
    3713          370 :          CALL extract_masked_blocks(mat_W_aux, used_ri, W_sub, compress_rows=.TRUE.)
    3714              : 
    3715              :          ! A_occ = phi_pan × G_occ × phi^T  (built first so W_pan can inherit its pattern).
    3716              :          ! With cutoff active, A_occ is pre-seeded with geo-local blocks so that the
    3717              :          ! phi^T multiply uses retain_sparsity and never computes distant blocks.
    3718          370 :          CALL create_product_matrix(phi_panC, G_occ_sub, 'N', 'N', tmpA)
    3719          370 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, phi_panC, G_occ_sub, 0.0_dp, tmpA, filter_eps=eps)
    3720          370 :          IF (use_cutoff) THEN
    3721              :             CALL build_geo_template_panel(phi_pan, phi_near, centroids, cutoff, blk0, A_occ, &
    3722           40 :                                           col_map=gmap)
    3723              :          ELSE
    3724          330 :             CALL create_product_matrix(tmpA, RB_phi, 'N', 'T', A_occ)
    3725              :          END IF
    3726              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_phi, 0.0_dp, A_occ, &
    3727          370 :                              filter_eps=eps, retain_sparsity=use_cutoff)
    3728          370 :          CALL dbcsr_release(tmpA)
    3729          370 :          CALL dbcsr_release(G_occ_sub)
    3730              : 
    3731              :          ! A_vir = phi_pan × G_vir × phi^T  (same pre-screen as A_occ)
    3732          370 :          CALL create_product_matrix(phi_panC, G_vir_sub, 'N', 'N', tmpA)
    3733          370 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, phi_panC, G_vir_sub, 0.0_dp, tmpA, filter_eps=eps)
    3734          370 :          IF (use_cutoff) THEN
    3735              :             CALL build_geo_template_panel(phi_pan, phi_near, centroids, cutoff, blk0, A_vir, &
    3736           40 :                                           col_map=gmap)
    3737              :          ELSE
    3738          330 :             CALL create_product_matrix(tmpA, RB_phi, 'N', 'T', A_vir)
    3739              :          END IF
    3740              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpA, RB_phi, 0.0_dp, A_vir, &
    3741          370 :                              filter_eps=eps, retain_sparsity=use_cutoff)
    3742          370 :          CALL dbcsr_release(tmpA)
    3743          370 :          CALL dbcsr_release(G_vir_sub)
    3744              : 
    3745              :          ! W_pan = Z_pan × W_aux × Z^T  (computed once, reused for both Σ^c terms).
    3746              :          ! With keep_sparsity, W_pan is pre-seeded with the union of A_occ and A_vir block
    3747              :          ! patterns so that retain_sparsity forces the multiply to fill only those blocks:
    3748              :          ! exact since W outside G_occ∪G_vir is multiplied by zero in the Hadamard.
    3749          370 :          CALL create_product_matrix(Z_panC, W_sub, 'N', 'N', tmpB)
    3750          370 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, Z_panC, W_sub, 0.0_dp, tmpB, filter_eps=eps)
    3751          370 :          IF (my_keep_sparsity) THEN
    3752          370 :             CALL dbcsr_create(W_pan, template=A_occ)
    3753          370 :             CALL dbcsr_copy(W_pan, A_occ)
    3754          370 :             CALL dbcsr_add(W_pan, A_vir, 1.0_dp, 1.0_dp)
    3755          370 :             CALL dbcsr_set(W_pan, 0.0_dp)
    3756              :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_Z, 0.0_dp, W_pan, &
    3757          370 :                                 filter_eps=eps, retain_sparsity=.TRUE.)
    3758              :          ELSE
    3759            0 :             CALL create_product_matrix(tmpB, RB_Z, 'N', 'T', W_pan)
    3760            0 :             CALL dbcsr_multiply('N', 'T', 1.0_dp, tmpB, RB_Z, 0.0_dp, W_pan, filter_eps=eps)
    3761              :          END IF
    3762          370 :          CALL dbcsr_release(tmpB)
    3763          370 :          CALL dbcsr_release(W_sub)
    3764          370 :          CALL dbcsr_release(phi_panC)
    3765          370 :          CALL dbcsr_release(Z_panC)
    3766              : 
    3767              :          ! Σ^c_neg: φ^T ( A_occ ∘ W_pan ) φ
    3768          370 :          CALL dbcsr_create(C_pan, template=A_occ)
    3769          370 :          CALL hadamard_product(A_occ, W_pan, C_pan, 1.0_dp)
    3770          370 :          CALL dbcsr_release(A_occ)
    3771          370 :          CALL create_product_matrix(C_pan, RB_phi, 'N', 'N', tmp2)
    3772          370 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_phi, 0.0_dp, tmp2, filter_eps=eps)
    3773          370 :          CALL dbcsr_release(C_pan)
    3774          370 :          CALL dbcsr_multiply('T', 'N', 1.0_dp, phi_pan, tmp2, 1.0_dp, mat_Sigma_neg, filter_eps=eps)
    3775          370 :          CALL dbcsr_release(tmp2)
    3776              : 
    3777              :          ! Σ^c_pos: φ^T ( A_vir ∘ W_pan ) φ  — W_pan reused
    3778          370 :          CALL dbcsr_create(C_pan, template=A_vir)
    3779          370 :          CALL hadamard_product(A_vir, W_pan, C_pan, 1.0_dp)
    3780          370 :          CALL dbcsr_release(A_vir)
    3781          370 :          CALL create_product_matrix(C_pan, RB_phi, 'N', 'N', tmp2)
    3782          370 :          CALL dbcsr_multiply('N', 'N', 1.0_dp, C_pan, RB_phi, 0.0_dp, tmp2, filter_eps=eps)
    3783          370 :          CALL dbcsr_release(C_pan)
    3784          370 :          CALL dbcsr_multiply('T', 'N', 1.0_dp, phi_pan, tmp2, 1.0_dp, mat_Sigma_pos, filter_eps=eps)
    3785          370 :          CALL dbcsr_release(tmp2)
    3786              : 
    3787          370 :          CALL dbcsr_release(W_pan)
    3788          370 :          CALL dbcsr_release(Z_pan)
    3789          370 :          CALL dbcsr_release(phi_pan)
    3790         2940 :          IF (use_cutoff) THEN
    3791           40 :             CALL dbcsr_release(phi_near)
    3792           40 :             CALL dbcsr_release(Z_near)
    3793              :          END IF
    3794              : 
    3795              :       END DO
    3796              : 
    3797          350 :       CALL timestop(handle)
    3798              : 
    3799          700 :    END SUBROUTINE contract_grid_panels_sigma_c
    3800              : 
    3801              : ! **************************************************************************************************
    3802              : !> \brief Computes C = A ◦ B (Element-wise Hadamard product) for sparse DBCSR matrices.
    3803              : !> \param matrix_A ...
    3804              : !> \param matrix_B ...
    3805              : !> \param matrix_C ...
    3806              : !> \param fac (Scaling factor applied to the product)
    3807              : ! **************************************************************************************************
    3808         2272 :    SUBROUTINE hadamard_product(matrix_A, matrix_B, matrix_C, fac)
    3809              : 
    3810              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix_A, matrix_B, matrix_C
    3811              :       REAL(KIND=dp), INTENT(IN)                          :: fac
    3812              : 
    3813              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'hadamard_product'
    3814              : 
    3815              :       INTEGER                                            :: col, handle, row
    3816              :       LOGICAL                                            :: found
    3817         1136 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: blk_B, blk_C
    3818              :       TYPE(dbcsr_iterator_type)                          :: iter
    3819              : 
    3820         1136 :       CALL timeset(routineN, handle)
    3821              : 
    3822         1136 :       CALL dbcsr_copy(matrix_C, matrix_A)
    3823              : 
    3824         1136 :       CALL dbcsr_iterator_start(iter, matrix_C)
    3825       140330 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    3826       139194 :          CALL dbcsr_iterator_next_block(iter, row, col, blk_C)
    3827              : 
    3828       139194 :          CALL dbcsr_get_block_p(matrix_B, row, col, blk_B, found)
    3829              : 
    3830       140330 :          IF (found) THEN
    3831    242822180 :             blk_C(:, :) = fac*blk_C(:, :)*blk_B(:, :)
    3832              :          ELSE
    3833              :             ! If B is sparse here, the product is zero
    3834            0 :             blk_C(:, :) = 0.0_dp
    3835              :          END IF
    3836              :       END DO
    3837         1136 :       CALL dbcsr_iterator_stop(iter)
    3838              : 
    3839         1136 :       CALL timestop(handle)
    3840              : 
    3841         1136 :    END SUBROUTINE hadamard_product
    3842              : 
    3843              : ! **************************************************************************************************
    3844              : !> \brief Computes the screened Coulomb interaction on the imaginary-time grid, entirely in the
    3845              : !>        RI auxiliary (PQ) basis:
    3846              : !>          χ_PQ(iω) = Σ_τ w(ω,τ) cos(ωτ) χ_PQ(iτ)                  (cosine transform)
    3847              : !>          ε(iω)    = Id - V^0.5 M^-1 χ(iω) M^-1 V^0.5             (dielectric function)
    3848              : !>          W(iω)    = V^0.5 ( ε^-1(iω) - Id ) V^0.5                (correlation part only)
    3849              : !>          W(iτ)    = Σ_ω w̃(τ,ω) cos(ωτ) W(iω)                    (back transform)
    3850              : !>          W(iτ)   <- M^-1 W(iτ) M^-1                              (fold in the RI metric)
    3851              : !>        where V is the bare Coulomb matrix and M the RI metric.
    3852              : !> \param bs_env ...
    3853              : !> \param qs_env ...
    3854              : !> \param mat_chi_Gamma_tau ...
    3855              : !> \param fm_W_time ...
    3856              : ! **************************************************************************************************
    3857           80 :    SUBROUTINE compute_W(bs_env, qs_env, mat_chi_Gamma_tau, fm_W_time)
    3858              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    3859              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    3860              :       TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: mat_chi_Gamma_tau
    3861              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_W_time
    3862              : 
    3863              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_W'
    3864              : 
    3865              :       INTEGER                                            :: handle, i_t, j_w
    3866              :       REAL(KIND=dp)                                      :: t1
    3867              :       TYPE(cp_fm_type)                                   :: fm_M_inv_V_sqrt, fm_V, fm_V_sqrt
    3868              : 
    3869           20 :       CALL timeset(routineN, handle)
    3870              : 
    3871           20 :       t1 = m_walltime()
    3872              : 
    3873           20 :       CALL create_fm_W_MIC_time(bs_env, fm_W_time)
    3874              : 
    3875              :       ! 1. Allocate V and M matrices
    3876           20 :       CALL cp_fm_create(fm_V, bs_env%fm_RI_RI%matrix_struct)
    3877           20 :       CALL cp_fm_create(fm_V_sqrt, bs_env%fm_RI_RI%matrix_struct)
    3878           20 :       CALL cp_fm_create(fm_M_inv_V_sqrt, bs_env%fm_RI_RI%matrix_struct)
    3879              : 
    3880              :       ! Compute V and M^-1 * V^0.5
    3881           20 :       CALL compute_V_MinvVsqrt(bs_env, qs_env, fm_V, fm_V_sqrt, fm_M_inv_V_sqrt)
    3882              : 
    3883              :       ! 2. Loop over frequencies
    3884          310 :       DO j_w = 1, bs_env%num_time_freq_points
    3885              :          ! Fourier transformation of χ_PQ(iτ) to χ_PQ(iω_j)
    3886          290 :          CALL compute_fm_chi_Gamma_freq(bs_env, bs_env%fm_chi_Gamma_freq, j_w, mat_chi_Gamma_tau)
    3887              : 
    3888              :          ! ε(iω_j) = Id - V^0.5*M^-1*χ(iω_j)*M^-1*V^0.5
    3889              :          ! W(iω_j) = V^0.5*(ε^-1(iω_j)-Id)*V^0.5
    3890              :          CALL compute_fm_W_freq(bs_env, bs_env%fm_chi_Gamma_freq, fm_V_sqrt, &
    3891          290 :                                 fm_M_inv_V_sqrt, bs_env%fm_W_MIC_freq)
    3892              : 
    3893              :          ! Fourier transform from W_PQ^MIC(iω_j) to W_PQ^MIC(iτ)
    3894          310 :          CALL Fourier_transform_w_to_t(bs_env, fm_W_time, bs_env%fm_W_MIC_freq, j_w)
    3895              :       END DO
    3896              : 
    3897              :       ! M^-1*W^MIC(iτ)*M^-1
    3898           20 :       CALL multiply_fm_W_MIC_time_with_Minv_Gamma(bs_env, qs_env, fm_W_time)
    3899              : 
    3900           20 :       IF (bs_env%unit_nr > 0) THEN
    3901              :          WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
    3902           10 :             'Computed W(iτ),', ' Execution time', m_walltime() - t1, ' s'
    3903              :       END IF
    3904              : 
    3905           20 :       CALL dbcsr_deallocate_matrix_set(mat_chi_Gamma_tau)
    3906              : 
    3907              :       ! Cleanup
    3908           20 :       CALL cp_fm_release(fm_V)
    3909           20 :       CALL cp_fm_release(fm_V_sqrt)
    3910           20 :       CALL cp_fm_release(fm_M_inv_V_sqrt)
    3911              : 
    3912              :       ! Marek : Fourier transform W^MIC(itau) back to get it at a specific im.frequency point - iomega = 0
    3913           20 :       IF (bs_env%rtp_method == rtp_method_bse) THEN
    3914            8 :          t1 = m_walltime()
    3915            8 :          CALL cp_fm_create(bs_env%fm_W_MIC_freq_zero, bs_env%fm_W_MIC_freq%matrix_struct)
    3916              :          ! Set to zero
    3917            8 :          CALL cp_fm_set_all(bs_env%fm_W_MIC_freq_zero, 0.0_dp)
    3918              :          ! Sum over all times
    3919          168 :          DO i_t = 1, bs_env%num_time_freq_points
    3920              :             ! Add the relevant structure with correct weight
    3921              :             CALL cp_fm_scale_and_add(1.0_dp, bs_env%fm_W_MIC_freq_zero, &
    3922          168 :                                      bs_env%imag_time_weights_freq_zero(i_t), fm_W_time(i_t))
    3923              :          END DO
    3924              :          ! Done, save to file
    3925            8 :          CALL fm_write(bs_env%fm_W_MIC_freq_zero, 0, "W_freq_rtp", qs_env)
    3926              :          ! Report calculation
    3927            8 :          IF (bs_env%unit_nr > 0) THEN
    3928              :             WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
    3929            4 :                'Computed W(0),', ' Execution time', m_walltime() - t1, ' s'
    3930              :          END IF
    3931              :       END IF
    3932              : 
    3933           20 :       IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
    3934              : 
    3935           20 :       CALL timestop(handle)
    3936              : 
    3937           20 :    END SUBROUTINE compute_W
    3938              : 
    3939              : ! **************************************************************************************************
    3940              : !> \brief Computes the static RI-basis Coulomb operators entering the dielectric function:
    3941              : !>        the bare Coulomb matrix V_PQ(k=0), its Cholesky/matrix square root V^0.5, and
    3942              : !>        M^-1 V^0.5 with M the RI metric (2c integrals of the RI_METRIC operator).
    3943              : !> \param bs_env ...
    3944              : !> \param qs_env ...
    3945              : !> \param fm_V ...
    3946              : !> \param fm_V_sqrt ...
    3947              : !> \param fm_Minv_Vsqrt ...
    3948              : ! **************************************************************************************************
    3949           20 :    SUBROUTINE compute_V_MinvVsqrt(bs_env, qs_env, fm_V, fm_V_sqrt, fm_Minv_Vsqrt)
    3950              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    3951              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    3952              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_V, fm_V_sqrt, fm_Minv_Vsqrt
    3953              : 
    3954              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_V_MinvVsqrt'
    3955              : 
    3956              :       INTEGER                                            :: handle, info, n_RI, ndep
    3957           20 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
    3958              :       TYPE(cell_type), POINTER                           :: cell
    3959              :       TYPE(cp_fm_type)                                   :: fm_work
    3960           20 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :)     :: fm_M
    3961           20 :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: mat_V_kp
    3962           20 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    3963           20 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    3964              : 
    3965           20 :       CALL timeset(routineN, handle)
    3966              : 
    3967           20 :       n_RI = bs_env%n_RI
    3968           20 :       CALL cp_fm_create(fm_work, fm_V%matrix_struct)
    3969              : 
    3970              :       ! -----------------------------------------------------------------------
    3971              :       ! 1. Build Coulomb Matrix V(k=0) using the kp-routine but only for ikp=1
    3972              :       ! -----------------------------------------------------------------------
    3973              :       CALL get_qs_env(qs_env=qs_env, particle_set=particle_set, cell=cell, &
    3974           20 :                       qs_kind_set=qs_kind_set, atomic_kind_set=atomic_kind_set)
    3975              : 
    3976          100 :       ALLOCATE (mat_V_kp(1:1, 1:2))
    3977           20 :       NULLIFY (mat_V_kp(1, 1)%matrix, mat_V_kp(1, 2)%matrix)
    3978           20 :       ALLOCATE (mat_V_kp(1, 1)%matrix, mat_V_kp(1, 2)%matrix)
    3979              : 
    3980           20 :       CALL dbcsr_create(mat_V_kp(1, 1)%matrix, template=bs_env%mat_RI_RI%matrix)
    3981           20 :       CALL dbcsr_reserve_all_blocks(mat_V_kp(1, 1)%matrix)
    3982           20 :       CALL dbcsr_set(mat_V_kp(1, 1)%matrix, 0.0_dp)
    3983              : 
    3984              :       ! Dummy imaginary part just to satisfy the routine
    3985           20 :       CALL dbcsr_create(mat_V_kp(1, 2)%matrix, template=bs_env%mat_RI_RI%matrix)
    3986           20 :       CALL dbcsr_reserve_all_blocks(mat_V_kp(1, 2)%matrix)
    3987           20 :       CALL dbcsr_set(mat_V_kp(1, 2)%matrix, 0.0_dp)
    3988              : 
    3989           80 :       bs_env%kpoints_chi_eps_W%nkp_grid = bs_env%nkp_grid_chi_eps_W_orig
    3990              : 
    3991              :       CALL build_2c_coulomb_matrix_kp(mat_V_kp, bs_env%kpoints_chi_eps_W, "RI_AUX", cell, &
    3992              :                                       particle_set, qs_kind_set, atomic_kind_set, &
    3993           20 :                                       bs_env%size_lattice_sum_V, operator_coulomb, 1, 1)
    3994              : 
    3995              :       ! Copy real part to fm_V
    3996           20 :       CALL copy_dbcsr_to_fm(mat_V_kp(1, 1)%matrix, fm_V)
    3997              : 
    3998           20 :       CALL dbcsr_deallocate_matrix(mat_V_kp(1, 1)%matrix)
    3999           20 :       CALL dbcsr_deallocate_matrix(mat_V_kp(1, 2)%matrix)
    4000           20 :       DEALLOCATE (mat_V_kp)
    4001              : 
    4002              :       ! -----------------------------------------------------------------------
    4003              :       ! 2. Get RI-Metric Matrix M(k=0)
    4004              :       ! -----------------------------------------------------------------------
    4005              :       CALL RI_2c_integral_mat(qs_env, fm_M, fm_V, n_RI, bs_env%ri_metric, &
    4006           20 :                               do_kpoints=.FALSE., regularization_RI=bs_env%regularization_RI)
    4007              : 
    4008              :       ! -----------------------------------------------------------------------
    4009              :       ! 3. M -> M^-1
    4010              :       ! -----------------------------------------------------------------------
    4011           20 :       CALL cp_fm_cholesky_decompose(fm_M(1, 1), info_out=info)
    4012           20 :       IF (info == 0) THEN
    4013           20 :          CALL cp_fm_cholesky_invert(fm_M(1, 1))
    4014           20 :          CALL cp_fm_uplo_to_full(fm_M(1, 1), fm_work)
    4015              :       ELSE
    4016              :          ! Fallback if Cholesky fails due to conditioning
    4017            0 :          CALL cp_fm_power(fm_M(1, 1), fm_work, -1.0_dp, bs_env%eps_eigval_mat_RI, ndep)
    4018            0 :          CALL cp_fm_to_fm(fm_work, fm_M(1, 1))
    4019              :       END IF
    4020              : 
    4021              :       ! -----------------------------------------------------------------------
    4022              :       ! 4. V -> V^0.5
    4023              :       ! -----------------------------------------------------------------------
    4024           20 :       CALL cp_fm_to_fm(fm_V, fm_V_sqrt)
    4025           20 :       CALL cp_fm_cholesky_decompose(fm_V_sqrt, info_out=info)
    4026           20 :       IF (info == 0) THEN
    4027           20 :          CALL clean_lower_part(fm_V_sqrt)
    4028              :       ELSE
    4029            0 :          CALL cp_fm_power(fm_V, fm_V_sqrt, 0.5_dp, bs_env%eps_eigval_mat_RI, ndep)
    4030              :       END IF
    4031              : 
    4032              :       ! -----------------------------------------------------------------------
    4033              :       ! 5. M^-1 * V^0.5
    4034              :       ! -----------------------------------------------------------------------
    4035              :       CALL parallel_gemm("N", "T", n_RI, n_RI, n_RI, 1.0_dp, fm_M(1, 1), fm_V_sqrt, &
    4036           20 :                          0.0_dp, fm_Minv_Vsqrt)
    4037              : 
    4038           20 :       CALL cp_fm_release(fm_M)
    4039           20 :       CALL cp_fm_release(fm_work)
    4040              : 
    4041           20 :       CALL timestop(handle)
    4042              : 
    4043           80 :    END SUBROUTINE compute_V_MinvVsqrt
    4044              : 
    4045              : ! **************************************************************************************************
    4046              : !> \brief Computes the screened interaction at one imaginary frequency:
    4047              : !>          ε(iω_j)  = Id - (M^-1 V^0.5)^T χ(iω_j) (M^-1 V^0.5)
    4048              : !>          W(iω_j)  = V^0.5^T ( ε^-1(iω_j) - Id ) V^0.5
    4049              : !>        ε is inverted via Cholesky; if that fails due to conditioning, via
    4050              : !>        eigendecomposition (cp_fm_power) with eigenvalue filtering.
    4051              : !> \param bs_env ...
    4052              : !> \param fm_chi_freq_j ...
    4053              : !> \param fm_V_sqrt ...
    4054              : !> \param fm_Minv_Vsqrt ...
    4055              : !> \param fm_W_freq_j ...
    4056              : ! **************************************************************************************************
    4057         1160 :    SUBROUTINE compute_fm_W_freq(bs_env, fm_chi_freq_j, fm_V_sqrt, fm_Minv_Vsqrt, fm_W_freq_j)
    4058              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    4059              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_chi_freq_j, fm_V_sqrt, fm_Minv_Vsqrt
    4060              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm_W_freq_j
    4061              : 
    4062              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_fm_W_freq'
    4063              : 
    4064              :       INTEGER                                            :: handle, info, n_RI, ndep
    4065              :       TYPE(cp_fm_type)                                   :: fm_eps_freq_j, fm_work
    4066              : 
    4067          290 :       CALL timeset(routineN, handle)
    4068              : 
    4069          290 :       n_RI = bs_env%n_RI
    4070              : 
    4071          290 :       CALL cp_fm_create(fm_eps_freq_j, fm_chi_freq_j%matrix_struct)
    4072          290 :       CALL cp_fm_create(fm_work, fm_chi_freq_j%matrix_struct)
    4073              : 
    4074              :       ! -----------------------------------------------------------------------
    4075              :       ! 1. ε(iω_j) = Id - (M^-1 * V^0.5)^T * χ(iω_j) * (M^-1 * V^0.5)
    4076              :       ! -----------------------------------------------------------------------
    4077              :       ! work = χ(iω_j) * (M^-1 * V^0.5)
    4078              :       CALL parallel_gemm('N', 'N', n_RI, n_RI, n_RI, 1.0_dp, &
    4079          290 :                          fm_chi_freq_j, fm_Minv_Vsqrt, 0.0_dp, fm_work)
    4080              : 
    4081              :       ! eps_work = (M^-1 * V^0.5)^T * work
    4082              :       CALL parallel_gemm('T', 'N', n_RI, n_RI, n_RI, 1.0_dp, &
    4083          290 :                          fm_Minv_Vsqrt, fm_work, 0.0_dp, fm_eps_freq_j)
    4084              : 
    4085              :       ! ε(iω_j) = Id - eps_work  -->  -eps_work + Id
    4086          290 :       CALL fm_add_on_diag(fm_eps_freq_j, 1.0_dp)
    4087              : 
    4088              :       ! Force perfect symmetry before Cholesky to avoid info != 0 due to GEMM noise
    4089          290 :       CALL cp_fm_uplo_to_full(fm_eps_freq_j, fm_work)
    4090              : 
    4091              :       ! -----------------------------------------------------------------------
    4092              :       ! 2. W(iω_j) = V^0.5^T * (ε^-1(iω_j) - Id) * V^0.5
    4093              :       ! -----------------------------------------------------------------------
    4094              : 
    4095              :       ! a) Cholesky decomposition of ε(iω_j)
    4096          290 :       CALL cp_fm_cholesky_decompose(fm_eps_freq_j, info_out=info)
    4097              : 
    4098              :       ! b) Inversion
    4099          290 :       IF (info == 0) THEN
    4100          290 :          CALL cp_fm_cholesky_invert(fm_eps_freq_j)
    4101          290 :          CALL cp_fm_uplo_to_full(fm_eps_freq_j, fm_work)
    4102              :       ELSE
    4103              :          ! Fallback to expensive diagonalization if Cholesky fails
    4104            0 :          CALL cp_fm_power(fm_eps_freq_j, fm_work, -1.0_dp, bs_env%eps_eigval_mat_RI, ndep)
    4105            0 :          CALL cp_fm_to_fm(fm_work, fm_eps_freq_j)
    4106              :       END IF
    4107              : 
    4108              :       ! c) ε^-1(iω_j) - Id
    4109          290 :       CALL fm_add_on_diag(fm_eps_freq_j, -1.0_dp)
    4110              : 
    4111              :       ! d) work = (ε^-1(iω_j) - Id) * V^0.5
    4112              :       CALL parallel_gemm('N', 'N', n_RI, n_RI, n_RI, 1.0_dp, fm_eps_freq_j, fm_V_sqrt, &
    4113          290 :                          0.0_dp, fm_work)
    4114              : 
    4115              :       ! e) W(iw) = V^0.5^T * work
    4116              :       CALL parallel_gemm('T', 'N', n_RI, n_RI, n_RI, 1.0_dp, fm_V_sqrt, fm_work, &
    4117          290 :                          0.0_dp, fm_W_freq_j)
    4118              : 
    4119              :       ! Cleanup
    4120          290 :       CALL cp_fm_release(fm_work)
    4121          290 :       CALL cp_fm_release(fm_eps_freq_j)
    4122              : 
    4123          290 :       CALL timestop(handle)
    4124              : 
    4125          290 :    END SUBROUTINE compute_fm_W_freq
    4126              : 
    4127              : ! **************************************************************************************************
    4128              : !> \brief Adds a real scalar value to the diagonal of a real full matrix
    4129              : !> \param fm ...
    4130              : !> \param alpha ...
    4131              : ! **************************************************************************************************
    4132         1160 :    SUBROUTINE fm_add_on_diag(fm, alpha)
    4133              :       TYPE(cp_fm_type), INTENT(INOUT)                    :: fm
    4134              :       REAL(KIND=dp), INTENT(IN)                          :: alpha
    4135              : 
    4136              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'fm_add_on_diag'
    4137              : 
    4138              :       INTEGER                                            :: handle, i_global, i_row, j_col, &
    4139              :                                                             j_global, ncol_local, nrow_local
    4140          580 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
    4141              : 
    4142          580 :       CALL timeset(routineN, handle)
    4143              : 
    4144              :       CALL cp_fm_get_info(matrix=fm, &
    4145              :                           nrow_local=nrow_local, &
    4146              :                           ncol_local=ncol_local, &
    4147              :                           row_indices=row_indices, &
    4148          580 :                           col_indices=col_indices)
    4149              : 
    4150        20800 :       DO j_col = 1, ncol_local
    4151        20220 :          j_global = col_indices(j_col)
    4152       417550 :          DO i_row = 1, nrow_local
    4153       396750 :             i_global = row_indices(i_row)
    4154       416970 :             IF (j_global == i_global) THEN
    4155        10110 :                fm%local_data(i_row, j_col) = fm%local_data(i_row, j_col) + alpha
    4156              :             END IF
    4157              :          END DO
    4158              :       END DO
    4159              : 
    4160          580 :       CALL timestop(handle)
    4161              : 
    4162          580 :    END SUBROUTINE fm_add_on_diag
    4163              : 
    4164              : ! **************************************************************************************************
    4165              : !> \brief Zeroes out the strictly lower triangular part of a real matrix
    4166              : !> \param fm_mat ...
    4167              : ! **************************************************************************************************
    4168           40 :    SUBROUTINE clean_lower_part(fm_mat)
    4169              :       TYPE(cp_fm_type)                                   :: fm_mat
    4170              : 
    4171              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'clean_lower_part'
    4172              : 
    4173              :       INTEGER                                            :: handle, i_row, j_col, j_global, &
    4174              :                                                             ncol_local, nrow_local
    4175           20 :       INTEGER, DIMENSION(:), POINTER                     :: col_indices, row_indices
    4176              : 
    4177           20 :       CALL timeset(routineN, handle)
    4178              : 
    4179              :       CALL cp_fm_get_info(matrix=fm_mat, &
    4180              :                           nrow_local=nrow_local, ncol_local=ncol_local, &
    4181           20 :                           row_indices=row_indices, col_indices=col_indices)
    4182              : 
    4183          642 :       DO j_col = 1, ncol_local
    4184          622 :          j_global = col_indices(j_col)
    4185        11795 :          DO i_row = 1, nrow_local
    4186        11775 :             IF (j_global < row_indices(i_row)) fm_mat%local_data(i_row, j_col) = 0.0_dp
    4187              :          END DO
    4188              :       END DO
    4189              : 
    4190           20 :       CALL timestop(handle)
    4191              : 
    4192           20 :    END SUBROUTINE clean_lower_part
    4193              : 
    4194              : ! **************************************************************************************************
    4195              : !> \brief Computes the exact-exchange part of the GW self-energy:
    4196              : !>          D_μν        = Σ_n^occ C_μn C_νn                     (density matrix = G^occ at τ=0)
    4197              : !>          V^tr_PQ     = M^-1 (P|Q)_trunc M^-1                 (truncated Coulomb, RI basis)
    4198              : !>          Σ^x_λσ(k=0) = -Σ_ll' Φ_λ(r_l) [ (φ D φ^T)_ll' ∘ (Z V^tr Z^T)_ll' ] Φ_σ(r_l')
    4199              : !> \param bs_env ...
    4200              : !> \param qs_env ...
    4201              : !> \param mat_phi_mu_l ...
    4202              : !> \param mat_Z_lP ...
    4203              : !> \param fm_Sigma_x_Gamma ...
    4204              : ! **************************************************************************************************
    4205           20 :    SUBROUTINE compute_Sigma_x(bs_env, qs_env, mat_phi_mu_l, mat_Z_lP, fm_Sigma_x_Gamma)
    4206              : 
    4207              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    4208              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    4209              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l, mat_Z_lP
    4210              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_Sigma_x_Gamma
    4211              : 
    4212              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_Sigma_x'
    4213              : 
    4214              :       INTEGER                                            :: handle, ispin
    4215           20 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    4216           20 :       INTEGER, DIMENSION(:), POINTER                     :: blk_aux, dist_row_aux
    4217              :       REAL(KIND=dp)                                      :: t1
    4218           20 :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :)     :: fm_Vtr_Gamma
    4219              :       TYPE(dbcsr_distribution_type)                      :: dist_aux_aux
    4220              :       TYPE(dbcsr_type)                                   :: mat_Sigma_x_Gamma, matrix_D_ao, &
    4221              :                                                             matrix_V_aux
    4222              : 
    4223           20 :       CALL timeset(routineN, handle)
    4224              : 
    4225           20 :       t1 = m_walltime()
    4226              : 
    4227           84 :       ALLOCATE (fm_Sigma_x_Gamma(bs_env%n_spin))
    4228           44 :       DO ispin = 1, bs_env%n_spin
    4229           44 :          CALL cp_fm_create(fm_Sigma_x_Gamma(ispin), bs_env%fm_s_Gamma%matrix_struct)
    4230              :       END DO
    4231              : 
    4232           20 :       CALL dbcsr_create(mat_Sigma_x_Gamma, template=bs_env%mat_ao_ao%matrix)
    4233              : 
    4234           20 :       CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    4235              : 
    4236              :       ! =========================================================================
    4237              :       ! 1. COMPUTE V^tr_PQ (RI x RI)
    4238              :       ! =========================================================================
    4239           20 :       CALL setup_square_topology(mat_Z_lP, dist_aux_aux, blk_aux, dist_row_aux)
    4240              : 
    4241              :       CALL RI_2c_integral_mat(qs_env, fm_Vtr_Gamma, bs_env%fm_RI_RI, bs_env%n_RI, &
    4242           20 :                               bs_env%trunc_coulomb, do_kpoints=.FALSE.)
    4243              : 
    4244              :       ! M^-1 * V^tr * M^-1 directly modifies fm_Vtr_Gamma(:, 1)
    4245           20 :       CALL multiply_fm_W_MIC_time_with_Minv_Gamma(bs_env, qs_env, fm_Vtr_Gamma(:, 1))
    4246              : 
    4247           20 :       CALL dbcsr_create(matrix_V_aux, "V_aux", dist_aux_aux, dbcsr_type_no_symmetry, blk_aux, blk_aux)
    4248              :       ! Optional CUTOFF_RADIUS_G_W operator truncation + filter (see build_G_ao)
    4249           20 :       IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
    4250              :           ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
    4251              :          CALL reserve_blocks_within_radius(matrix_V_aux, bs_env%ri_rs%atom_centers, &
    4252            0 :                                            bs_env%ri_rs%cutoff_radius_g_w)
    4253            0 :          CALL copy_fm_to_dbcsr(fm_Vtr_Gamma(1, 1), matrix_V_aux, keep_sparsity=.TRUE.)
    4254              :       ELSE
    4255           20 :          CALL copy_fm_to_dbcsr(fm_Vtr_Gamma(1, 1), matrix_V_aux, keep_sparsity=.FALSE.)
    4256              :       END IF
    4257           20 :       CALL dbcsr_filter(matrix_V_aux, bs_env%eps_filter)
    4258              : 
    4259              :       ! =========================================================================
    4260              :       ! 2. SPIN LOOP FOR EXACT EXCHANGE
    4261              :       ! Σ^x_λσ = -Σ_ll' Φ_λ(r_l) ( D_ll' V^tr_ll' ) Φ_σ(r_l')
    4262              :       !        = -φ^T ( (φ D φ^T) ∘ (Z V^tr Z^T) ) φ
    4263              :       ! =========================================================================
    4264           44 :       DO ispin = 1, bs_env%n_spin
    4265              : 
    4266              :          ! AO-space density matrix D_µν = G^occ at τ = 0
    4267           24 :          CALL build_G_ao(bs_env, 0.0_dp, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_D_ao)
    4268              : 
    4269              :          CALL contract_grid_panels(L_A=mat_phi_mu_l, M_A=matrix_D_ao, &
    4270              :                                    L_B=mat_Z_lP, M_B=matrix_V_aux, &
    4271              :                                    L_out=mat_phi_mu_l, mat_out=mat_Sigma_x_Gamma, &
    4272              :                                    scale=1.0_dp, eps=bs_env%eps_filter, &
    4273              :                                    para_env=bs_env%para_env, &
    4274              :                                    pan_first=pan_first, pan_last=pan_last, &
    4275              :                                    lb_eq_la=.FALSE., lout_eq_la=.TRUE., zero_out=.TRUE., &
    4276              :                                    keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
    4277              :                                    centroids=bs_env%ri_rs%chunk_centroids, &
    4278           24 :                                    cutoff=bs_env%ri_rs%cutoff_radius_v_w)
    4279           24 :          CALL dbcsr_scale(mat_Sigma_x_Gamma, -1.0_dp)
    4280              : 
    4281           24 :          CALL dbcsr_release(matrix_D_ao)
    4282              : 
    4283              :          ! Data I/O and Export to CP2K Full Matrices
    4284           44 :          CALL copy_dbcsr_to_fm(mat_Sigma_x_Gamma, fm_Sigma_x_Gamma(ispin))
    4285              : 
    4286              :       END DO ! ispin
    4287              : 
    4288           20 :       IF (bs_env%unit_nr > 0) THEN
    4289              :          WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
    4290           10 :             'Computed Σ^x(k=0),', ' Execution time', m_walltime() - t1, ' s'
    4291           10 :          WRITE (bs_env%unit_nr, '(A)') ' '
    4292              :       END IF
    4293              : 
    4294              :       ! =========================================================================
    4295              :       ! 3. CLEANUP
    4296              :       ! =========================================================================
    4297           20 :       CALL dbcsr_release(matrix_V_aux)
    4298           20 :       CALL dbcsr_release(mat_Sigma_x_Gamma)
    4299           20 :       CALL release_square_topology(dist=dist_aux_aux, mapped_dist=dist_row_aux)
    4300              : 
    4301           20 :       CALL cp_fm_release(fm_Vtr_Gamma)
    4302              : 
    4303           20 :       CALL timestop(handle)
    4304              : 
    4305           60 :    END SUBROUTINE compute_Sigma_x
    4306              : 
    4307              : ! **************************************************************************************************
    4308              : !> \brief Computes the correlation part of the GW self-energy on the imaginary-time grid:
    4309              : !>          Σ^c_λσ(iτ<0) = -Σ_ll' Φ_λ(r_l) [ (φ G^occ φ^T)_ll' ∘ (Z W^MIC Z^T)_ll' ] Φ_σ(r_l')
    4310              : !>          Σ^c_λσ(iτ>0) = +Σ_ll' Φ_λ(r_l) [ (φ G^vir φ^T)_ll' ∘ (Z W^MIC Z^T)_ll' ] Φ_σ(r_l')
    4311              : !> \param bs_env ...
    4312              : !> \param fm_W_time ...
    4313              : !> \param mat_phi_mu_l ...
    4314              : !> \param mat_Z_lP ...
    4315              : !> \param fm_Sigma_c_Gamma_time ...
    4316              : ! **************************************************************************************************
    4317           20 :    SUBROUTINE compute_Sigma_c(bs_env, fm_W_time, mat_phi_mu_l, mat_Z_lP, fm_Sigma_c_Gamma_time)
    4318              : 
    4319              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    4320              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)        :: fm_W_time
    4321              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l, mat_Z_lP
    4322              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:, :, :)  :: fm_Sigma_c_Gamma_time
    4323              : 
    4324              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_Sigma_c'
    4325              : 
    4326              :       INTEGER                                            :: handle, i_t, ispin
    4327           20 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: pan_first, pan_last
    4328           20 :       INTEGER, DIMENSION(:), POINTER                     :: blk_aux, dist_row_aux
    4329              :       REAL(KIND=dp)                                      :: t1, tau
    4330              :       TYPE(dbcsr_distribution_type)                      :: dist_aux_aux
    4331           20 :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: mat_Sigma_neg_tau, mat_Sigma_pos_tau
    4332              :       TYPE(dbcsr_type)                                   :: matrix_G_occ_ao, matrix_G_vir_ao, &
    4333              :                                                             matrix_W_aux
    4334              : 
    4335           20 :       CALL timeset(routineN, handle)
    4336              : 
    4337              :       ! =========================================================================
    4338              :       ! 1. SETUP AUXILIARY TOPOLOGY AND PRE-ALLOCATE OUTPUT ARRAYS
    4339              :       ! =========================================================================
    4340           20 :       CALL setup_square_topology(mat_Z_lP, dist_aux_aux, blk_aux, dist_row_aux)
    4341              : 
    4342           20 :       CALL resolve_grid_panels(bs_env, mat_phi_mu_l, pan_first, pan_last)
    4343              : 
    4344              :       ! Pre-allocate local DBCSR matrices to act as targets for final output
    4345           20 :       NULLIFY (mat_Sigma_neg_tau, mat_Sigma_pos_tau)
    4346          454 :       ALLOCATE (mat_Sigma_neg_tau(bs_env%num_time_freq_points, bs_env%n_spin))
    4347          454 :       ALLOCATE (mat_Sigma_pos_tau(bs_env%num_time_freq_points, bs_env%n_spin))
    4348              : 
    4349          310 :       DO i_t = 1, bs_env%num_time_freq_points
    4350          660 :          DO ispin = 1, bs_env%n_spin
    4351          350 :             ALLOCATE (mat_Sigma_neg_tau(i_t, ispin)%matrix)
    4352          350 :             ALLOCATE (mat_Sigma_pos_tau(i_t, ispin)%matrix)
    4353          350 :             CALL dbcsr_create(mat_Sigma_neg_tau(i_t, ispin)%matrix, template=bs_env%mat_ao_ao%matrix)
    4354          640 :             CALL dbcsr_create(mat_Sigma_pos_tau(i_t, ispin)%matrix, template=bs_env%mat_ao_ao%matrix)
    4355              :          END DO
    4356              :       END DO
    4357              : 
    4358              :       ! =========================================================================
    4359              :       ! 2. IMAGINARY TIME LOOP
    4360              :       ! Σ^c_neg_λσ(iτ) = -φ^T ( (φ G^occ φ^T) ∘ (Z W^MIC Z^T) ) φ
    4361              :       ! Σ^c_pos_λσ(iτ) =  φ^T ( (φ G^vir φ^T) ∘ (Z W^MIC Z^T) ) φ
    4362              :       ! =========================================================================
    4363          310 :       DO i_t = 1, bs_env%num_time_freq_points
    4364          290 :          tau = bs_env%imag_time_points(i_t)
    4365              : 
    4366          290 :          CALL dbcsr_create(matrix_W_aux, "W_aux", dist_aux_aux, dbcsr_type_no_symmetry, blk_aux, blk_aux)
    4367          290 :          IF (bs_env%ri_rs%cutoff_radius_g_w > 0.0_dp .AND. &
    4368              :              ALLOCATED(bs_env%ri_rs%atom_centers)) THEN
    4369              :             CALL reserve_blocks_within_radius(matrix_W_aux, bs_env%ri_rs%atom_centers, &
    4370            0 :                                               bs_env%ri_rs%cutoff_radius_g_w)
    4371            0 :             CALL copy_fm_to_dbcsr(fm_W_time(i_t), matrix_W_aux, keep_sparsity=.TRUE.)
    4372              :          ELSE
    4373          290 :             CALL copy_fm_to_dbcsr(fm_W_time(i_t), matrix_W_aux, keep_sparsity=.FALSE.)
    4374              :          END IF
    4375          290 :          CALL dbcsr_filter(matrix_W_aux, bs_env%eps_filter)
    4376              : 
    4377          640 :          DO ispin = 1, bs_env%n_spin
    4378          350 :             t1 = m_walltime()
    4379              : 
    4380              :             ! AO-space Green's functions G^occ_µν, G^vir_µν (dense AO x AO, small)
    4381          350 :             CALL build_G_ao(bs_env, tau, ispin, .TRUE., .FALSE., mat_phi_mu_l, matrix_G_occ_ao)
    4382          350 :             CALL build_G_ao(bs_env, tau, ispin, .FALSE., .TRUE., mat_phi_mu_l, matrix_G_vir_ao)
    4383              : 
    4384              :             ! Σ^c_neg and Σ^c_pos in a single panel loop: W_pan = Z_panel × W × Z^T built once
    4385              :             CALL contract_grid_panels_sigma_c(mat_phi=mat_phi_mu_l, mat_Z=mat_Z_lP, &
    4386              :                                               mat_G_occ_ao=matrix_G_occ_ao, &
    4387              :                                               mat_G_vir_ao=matrix_G_vir_ao, &
    4388              :                                               mat_W_aux=matrix_W_aux, &
    4389              :                                               mat_Sigma_neg=mat_Sigma_neg_tau(i_t, ispin)%matrix, &
    4390              :                                               mat_Sigma_pos=mat_Sigma_pos_tau(i_t, ispin)%matrix, &
    4391              :                                               eps=bs_env%eps_filter, &
    4392              :                                               para_env=bs_env%para_env, &
    4393              :                                               pan_first=pan_first, pan_last=pan_last, &
    4394              :                                               keep_sparsity=bs_env%ri_rs%keep_sparsity_rirs, &
    4395              :                                               centroids=bs_env%ri_rs%chunk_centroids, &
    4396          350 :                                               cutoff=bs_env%ri_rs%cutoff_radius_v_w)
    4397          350 :             CALL dbcsr_scale(mat_Sigma_neg_tau(i_t, ispin)%matrix, -1.0_dp)
    4398              : 
    4399          350 :             CALL dbcsr_release(matrix_G_occ_ao)
    4400          350 :             CALL dbcsr_release(matrix_G_vir_ao)
    4401              : 
    4402          640 :             IF (bs_env%unit_nr > 0) THEN
    4403              :                WRITE (bs_env%unit_nr, '(T2,A,I15,A,I3,A,F7.1,A)') &
    4404          175 :                   'Computed Σ^c(iτ) for time point', i_t, ' /', bs_env%num_time_freq_points, &
    4405          350 :                   ',    Execution time', m_walltime() - t1, ' s'
    4406              :             END IF
    4407              : 
    4408              :          END DO ! ispin
    4409              : 
    4410          310 :          CALL dbcsr_release(matrix_W_aux)
    4411              : 
    4412              :       END DO ! i_t
    4413              : 
    4414           20 :       IF (bs_env%unit_nr > 0) WRITE (bs_env%unit_nr, '(A)') ' '
    4415              : 
    4416              :       ! -------------------------------------------------------------------------
    4417              :       ! 3. FINALIZE AND CLEANUP
    4418              :       ! -------------------------------------------------------------------------
    4419              :       CALL fill_fm_Sigma_c_Gamma_time(fm_Sigma_c_Gamma_time, bs_env, &
    4420           20 :                                       mat_Sigma_pos_tau, mat_Sigma_neg_tau)
    4421              : 
    4422           20 :       CALL cp_fm_release(fm_W_time)
    4423              : 
    4424           20 :       CALL dbcsr_deallocate_matrix_set(mat_Sigma_neg_tau)
    4425           20 :       CALL dbcsr_deallocate_matrix_set(mat_Sigma_pos_tau)
    4426              : 
    4427           20 :       CALL release_square_topology(dist=dist_aux_aux, mapped_dist=dist_row_aux)
    4428              : 
    4429           20 :       CALL delete_unnecessary_files(bs_env)
    4430           20 :       CALL timestop(handle)
    4431              : 
    4432           40 :    END SUBROUTINE compute_Sigma_c
    4433              : 
    4434              : ! **************************************************************************************************
    4435              : !> \brief Builds the DBCSR distribution.
    4436              : !> \param matrix_template ...
    4437              : !> \param square_dist ...
    4438              : !> \param blk_sizes ...
    4439              : !> \param mapped_dist ...
    4440              : ! **************************************************************************************************
    4441         1464 :    SUBROUTINE setup_square_topology(matrix_template, square_dist, blk_sizes, mapped_dist)
    4442              : 
    4443              :       TYPE(dbcsr_type), INTENT(IN)                       :: matrix_template
    4444              :       TYPE(dbcsr_distribution_type), INTENT(OUT)         :: square_dist
    4445              :       INTEGER, DIMENSION(:), INTENT(OUT), POINTER        :: blk_sizes, mapped_dist
    4446              : 
    4447              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'setup_square_topology'
    4448              : 
    4449              :       INTEGER                                            :: handle, i, nprows
    4450         1464 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk, col_dist
    4451              :       TYPE(dbcsr_distribution_type)                      :: dist_template
    4452              : 
    4453         1464 :       CALL timeset(routineN, handle)
    4454              : 
    4455         1464 :       CALL dbcsr_get_info(matrix_template, distribution=dist_template, col_blk_size=col_blk)
    4456         1464 :       CALL dbcsr_distribution_get(dist_template, col_dist=col_dist, nprows=nprows)
    4457              : 
    4458         1464 :       blk_sizes => col_blk
    4459         4392 :       ALLOCATE (mapped_dist(SIZE(blk_sizes)))
    4460         4862 :       DO i = 1, SIZE(blk_sizes)
    4461         4862 :          mapped_dist(i) = MOD(i - 1, nprows)
    4462              :       END DO
    4463              :       CALL dbcsr_distribution_new(square_dist, template=dist_template, &
    4464         1464 :                                   row_dist=mapped_dist, col_dist=col_dist)
    4465              : 
    4466         1464 :       CALL timestop(handle)
    4467              : 
    4468         2928 :    END SUBROUTINE setup_square_topology
    4469              : 
    4470              : ! **************************************************************************************************
    4471              : !> \brief Releases a distribution created by setup_square_topology.
    4472              : !> \param dist ...
    4473              : !> \param mapped_dist ...
    4474              : ! **************************************************************************************************
    4475         1464 :    SUBROUTINE release_square_topology(dist, mapped_dist)
    4476              : 
    4477              :       TYPE(dbcsr_distribution_type), INTENT(INOUT)       :: dist
    4478              :       INTEGER, DIMENSION(:), INTENT(INOUT), POINTER      :: mapped_dist
    4479              : 
    4480         1464 :       CALL dbcsr_distribution_release(dist)
    4481         1464 :       IF (ASSOCIATED(mapped_dist)) THEN
    4482         1464 :          DEALLOCATE (mapped_dist)
    4483              :          NULLIFY (mapped_dist)
    4484              :       END IF
    4485              : 
    4486         1464 :    END SUBROUTINE release_square_topology
    4487              : 
    4488              : END MODULE gw_non_periodic_ri_rs
        

Generated by: LCOV version 2.0-1