LCOV - code coverage report
Current view: top level - src - ri_rs_setup.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:123285f) Lines: 61.9 % 1113 689
Test Date: 2026-11-04 00:58:17 Functions: 82.1 % 28 23

            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 Builds the shared RI-RS grid, AO collocation and fitting representation.
      10              : !> \par History
      11              : !>      09.2026 created Jan Wilhelm
      12              : !>      09.2026 moved code by Ritaj Tyagi from gw_ri_rs_non_periodic.F
      13              : !>      09.2026 added routine to compute Z_lP for automatic RI optimization
      14              : ! **************************************************************************************************
      15              : MODULE ri_rs_setup
      16              :    USE ai_contraction_sphi,             ONLY: abc_contract_xsmm
      17              :    USE atomic_kind_types,               ONLY: atomic_kind_type
      18              :    USE basis_set_types,                 ONLY: get_gto_basis_set
      19              :    USE cell_types,                      ONLY: cell_type,&
      20              :                                               pbc
      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_binary_read, dbcsr_binary_write, dbcsr_create, dbcsr_distribution_get, &
      26              :         dbcsr_distribution_new, dbcsr_distribution_release, dbcsr_distribution_type, dbcsr_filter, &
      27              :         dbcsr_finalize, dbcsr_get_block_p, dbcsr_get_info, dbcsr_multiply, dbcsr_put_block, &
      28              :         dbcsr_release, dbcsr_set, dbcsr_type, dbcsr_type_no_symmetry
      29              :    USE cp_dbcsr_contrib,                ONLY: dbcsr_reserve_all_blocks
      30              :    USE cp_log_handling,                 ONLY: cp_get_default_logger,&
      31              :                                               cp_logger_type
      32              :    USE cp_output_handling,              ONLY: cp_p_file,&
      33              :                                               cp_print_key_should_output
      34              :    USE gw_auto_ri_types,                ONLY: auto_ri_type
      35              :    USE gw_utils_compute_integrals,      ONLY: build_3c_integral_block_ctx,&
      36              :                                               gw_3c_ctx_create,&
      37              :                                               gw_3c_ctx_release,&
      38              :                                               gw_3c_ctx_type,&
      39              :                                               gw_3c_ws_create,&
      40              :                                               gw_3c_ws_release,&
      41              :                                               gw_3c_ws_type
      42              :    USE input_section_types,             ONLY: section_vals_type
      43              :    USE kinds,                           ONLY: dp,&
      44              :                                               max_line_length
      45              :    USE libint_2c_3c,                    ONLY: eri_3center
      46              :    USE machine,                         ONLY: m_flush,&
      47              :                                               m_walltime
      48              :    USE message_passing,                 ONLY: mp_mem_avail_per_rank_GB,&
      49              :                                               mp_para_env_type
      50              :    USE orbital_pointers,                ONLY: ncoset
      51              :    USE particle_types,                  ONLY: particle_type
      52              :    USE physcon,                         ONLY: angstrom
      53              :    USE post_scf_bandstructure_types,    ONLY: post_scf_bandstructure_type
      54              :    USE qs_environment_types,            ONLY: get_qs_env,&
      55              :                                               qs_environment_type
      56              :    USE qs_kind_types,                   ONLY: qs_kind_type
      57              :    USE ri_rs_fitting,                   ONLY: solve_ri_rs_fit,&
      58              :                                               store_Z_lP_columns
      59              :    USE ri_rs_grid_setup,                ONLY: setup_ri_rs_grid
      60              :    USE ri_rs_matrices,                  ONLY: atomic_basis_at_grid_point,&
      61              :                                               print_matrix_occupation
      62              :    USE ri_rs_types,                     ONLY: ri_rs_env
      63              :    USE ri_rs_utils,                     ONLY: evaluate_ao_basis_on_points,&
      64              :                                               evaluate_ao_on_points,&
      65              :                                               precompute_ri_rs_radii
      66              :    USE util,                            ONLY: locate,&
      67              :                                               sort
      68              : #include "./base/base_uses.f90"
      69              : 
      70              :    IMPLICIT NONE
      71              :    PRIVATE
      72              : 
      73              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'ri_rs_setup'
      74              : 
      75              :    PUBLIC :: compute_Z_lP, ensure_ri_rs_representation
      76              : 
      77              : CONTAINS
      78              : 
      79              : ! **************************************************************************************************
      80              : !> \brief Populate the RI-RS grid, AO collocation and auxiliary fitting coefficients once.
      81              : !>        Periodic systems use the same neighbouring-image convention for phi and Z.
      82              : !> \param qs_env electronic-structure environment
      83              : !> \param bs_env geometry, RI-RS settings and owned output matrices
      84              : ! **************************************************************************************************
      85           84 :    SUBROUTINE ensure_ri_rs_representation(qs_env, bs_env)
      86              :       TYPE(qs_environment_type), POINTER                 :: qs_env
      87              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
      88              : 
      89              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'ensure_ri_rs_representation'
      90              : 
      91              :       INTEGER                                            :: handle
      92              :       REAL(KIND=dp)                                      :: build_start
      93              : 
      94           84 :       CALL timeset(routineN, handle)
      95           84 :       IF (.NOT. bs_env%ri_rs%grid_built) THEN
      96           48 :          build_start = m_walltime()
      97              :          CALL get_qs_env(qs_env, atomic_kind_set=bs_env%ri_rs%atomic_kind_set, &
      98           48 :                          cell=bs_env%ri_rs%cell, particle_set=bs_env%ri_rs%particle_set)
      99              :          ! AO/RI support radii determine which atoms and grid points enter each local fit.
     100           48 :          IF (.NOT. ALLOCATED(bs_env%ri_rs%radius_ao_per_atom)) THEN
     101              :             CALL precompute_ri_rs_radii(bs_env%ri_rs, bs_env%basis_set_AO, bs_env%basis_set_RI, &
     102           48 :                                         bs_env%eps_filter, bs_env%unit_nr)
     103              :          END IF
     104              :          ! Place atom-centered RI-RS grids at r_l = R_A + r_l^(A), then flatten them into one grid.
     105           48 :          CALL setup_ri_rs_grid(bs_env, bs_env%ri_rs%grid_points)
     106              :          ! Collocation matrix ϕ_μl = ϕ_μ(r_l), including neighbouring AO images for Gamma.
     107              :          CALL atomic_basis_at_grid_point(bs_env%ri_rs, bs_env%basis_set_AO, &
     108              :                                          bs_env%mat_ao_ao%matrix, bs_env%para_env, bs_env%eps_filter, &
     109              :                                          bs_env%dbcsr_msg_elem_limit, bs_env%unit_nr, &
     110           48 :                                          periodic_images=bs_env%do_periodic)
     111              :          CALL print_matrix_occupation(bs_env%ri_rs%mat_phi_mu_l, 'ϕ_μ(r_l)', &
     112           48 :                                       bs_env%para_env, bs_env%unit_nr, bs_env%dbcsr_msg_elem_limit)
     113              :          ! Fit Z_lP using the integration domains and RI basis described in compute_Z_lP below.
     114              :          CALL compute_Z_lP(qs_env, bs_env, bs_env%ri_rs%grid_points, &
     115              :                            bs_env%ri_rs%mat_phi_mu_l, bs_env%ri_rs%mat_Z_lP, &
     116           48 :                            periodic_images=bs_env%do_periodic)
     117           48 :          bs_env%ri_rs%grid_built = .TRUE.
     118           48 :          IF (bs_env%unit_nr > 0) THEN
     119              :             WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A)') &
     120           24 :                'Built RI-RS representation,', ' Execution time', m_walltime() - build_start, ' s'
     121           24 :             WRITE (bs_env%unit_nr, '(A)') ' '
     122              :          END IF
     123              :       END IF
     124           84 :       CALL timestop(handle)
     125           84 :    END SUBROUTINE ensure_ri_rs_representation
     126              : 
     127              : ! **************************************************************************************************
     128              : !> \brief Computes Z_lP for (1) a tabulated RI basis set or (2) an on-the-fly generated RI basis set.
     129              : !>
     130              : !>        In both cases, the Z_lP coefficients are computed by solving the linear system
     131              : !>
     132              : !>          Σ_l' D_ll' Z_l'P = d_lP,
     133              : !>
     134              : !>        with D_ll' and d_lP given by
     135              : !>
     136              : !>          D_ll' = [Σ_μ ϕ_μ(r_l) ϕ_μ(r_l')]²,
     137              : !>
     138              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P).
     139              : !>
     140              : !>        For an on-the-fly generated RI basis set, an RI function φ_P may be a contraction of
     141              : !>        Gaussians on neighboring atoms, which requires special computation of d_lP.
     142              : !>
     143              : !> \param qs_env ...
     144              : !> \param bs_env Band-structure environment containing GW parameters.
     145              : !> \param ri_rs_grid_points ...
     146              : !> \param mat_phi_mu_l ...
     147              : !> \param mat_Z_lP ...
     148              : !> \param periodic_images use Gamma image sums and minimum-image integration spheres
     149              : ! **************************************************************************************************
     150           48 :    SUBROUTINE compute_Z_lP(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP, periodic_images)
     151              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     152              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     153              :       REAL(KIND=dp), INTENT(IN)                          :: ri_rs_grid_points(:, :)
     154              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     155              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     156              :       LOGICAL, INTENT(IN), OPTIONAL                      :: periodic_images
     157              : 
     158              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_Z_lP'
     159              : 
     160              :       INTEGER                                            :: handle
     161              :       LOGICAL                                            :: sum_images
     162              :       REAL(KIND=dp)                                      :: fitting_stats(2)
     163              : 
     164           48 :       CALL timeset(routineN, handle)
     165           48 :       sum_images = .FALSE.
     166           48 :       IF (PRESENT(periodic_images)) sum_images = periodic_images
     167              : 
     168           48 :       bs_env%ri_rs%fit_max_grid = 0
     169           48 :       bs_env%ri_rs%fit_peak_bytes = 0.0_dp
     170           48 :       IF (bs_env%auto_ri%enabled) THEN
     171            4 :          CALL compute_Z_lP_auto_ri(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     172              :          ! Localized AUTO-RI fits can be owned by different process columns.
     173           12 :          fitting_stats = [REAL(bs_env%ri_rs%fit_max_grid, dp), bs_env%ri_rs%fit_peak_bytes]
     174            4 :          CALL bs_env%para_env%max(fitting_stats)
     175            4 :          bs_env%ri_rs%fit_max_grid = NINT(fitting_stats(1))
     176            4 :          bs_env%ri_rs%fit_peak_bytes = fitting_stats(2)
     177              :       ELSE
     178           44 :          CALL compute_Z_lP_standard(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP, sum_images)
     179              :       END IF
     180              : 
     181           48 :       CALL timestop(handle)
     182              : 
     183           48 :    END SUBROUTINE compute_Z_lP
     184              : 
     185              : ! **************************************************************************************************
     186              : !> \brief Computes the RI-RS fitting coefficients Z_lP by solving, independently for every RI
     187              : !>        atom P, a Jacobi-conditioned, Tikhonov-regularized linear system restricted to the
     188              : !>        grid points r_l inside P's integration sphere |r_l - R_P| <= cutoff_ri(P):
     189              : !>
     190              : !>        1.  D_ll'  = [ Σ_μ ϕ_μ(r_l) ϕ_μ(r_l') ]²
     191              : !>        2.  d_l    = 1 / sqrt(max(D_ll, jacobi_floor)) (Jacobi conditioning vector)
     192              : !>        3.  D'_ll' = d_l D_ll' d_l' + λ δ_ll'         (λ = TIKHONOV regularization)
     193              : !>        4.  d_lP   = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P)
     194              : !>        5.  Σ_l' D'_ll' Z'_l'P = d_l d_lP             (solve linear system for Z'_lP)
     195              : !>        6.  Z_lP   = d_l Z'_lP                        (undo the conditioning)
     196              : !>
     197              : !>        See Duchemin and Blase, arXiv:1902.00066, Sec. II.3 for the least-squares construction
     198              : !>        and balancing, and solve_ri_rs_fit for the common floor and regularization convention.
     199              : !>
     200              : !>        The Gamma path uses image-summed AO values and three-center integrals, with RI
     201              : !>        atom P fixed in the reference cell. Molecular and Gamma sphere cutoffs are retained.
     202              : !>
     203              : !>        Work is distributed over atoms in two phases (planned by classify_z_lp_atoms and
     204              : !>        lpt_assign_atoms): Phase A solves "small" atoms with single-rank LAPACK
     205              : !>        (dpotrf/dpotrs); Phase B solves "big" atoms, whose dense matrix D'_ll' would exceed one
     206              : !>        rank's memory, with ScaLAPACK (pdpotrf/pdpotrs) over rank subgroups of size G.
     207              : !>        The solved Z columns are scattered into the sparse global mat_Z_lP.
     208              : !>        If a Z_lP restart file exists, it is read instead and the solve is skipped entirely.
     209              : !> \param qs_env ...
     210              : !> \param bs_env ...
     211              : !> \param ri_rs_grid_points ...
     212              : !> \param mat_phi_mu_l ...
     213              : !> \param mat_Z_lP ...
     214              : !> \param periodic_images use Gamma image sums and minimum-image integration spheres
     215              : ! **************************************************************************************************
     216           44 :    SUBROUTINE compute_Z_lP_standard(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP, periodic_images)
     217              : 
     218              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     219              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     220              :       REAL(KIND=dp), INTENT(IN)                          :: ri_rs_grid_points(:, :)
     221              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     222              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     223              :       LOGICAL, INTENT(IN)                                :: periodic_images
     224              : 
     225              :       CHARACTER(LEN=*), PARAMETER :: key = 'PROPERTIES%BANDSTRUCTURE%GW%PRINT%RESTART', &
     226              :          routineN = 'compute_Z_lP_standard'
     227              : 
     228              :       INTEGER :: atom_j_mepos, atom_j_stride, atom_P, G, handle, i_blk, iatom, idx, iphase, j, &
     229              :          max_ao_size, my_group, n_big, n_done, n_groups, n_loc_ri, n_local_grid, n_my_atoms, &
     230              :          n_small, natom, npcol_phi, num_grid_chunks, phase_hi
     231           88 :       INTEGER, ALLOCATABLE, DIMENSION(:) :: ao_atoms, ao_col_start, big_list, local_grid_idx, &
     232           44 :          my_atoms_A, my_atoms_B, n_local_grid_atom, row_offset, small_list
     233           88 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist_ri, r_blk_sizes, ri_blk_sizes, &
     234           44 :                                                             row_dist_grid
     235              :       LOGICAL                                            :: do_scatter, use_dist
     236           44 :       LOGICAL, ALLOCATABLE                               :: ao_nonzero(:, :)
     237              :       REAL(KIND=dp)                                      :: cutoff_ri, item_start_time, r_c, t1
     238           44 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cutoff_ri_per_atom
     239           44 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_local, phi_local
     240              :       TYPE(cell_type), POINTER                           :: cell
     241              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env_sub
     242              :       TYPE(cp_logger_type), POINTER                      :: logger
     243              :       TYPE(dbcsr_distribution_type)                      :: dist_phi, dist_Z
     244          572 :       TYPE(gw_3c_ctx_type)                               :: ctx_3c
     245              :       TYPE(mp_para_env_type), POINTER                    :: para_env, para_env_sub
     246           44 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     247           44 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     248              :       TYPE(section_vals_type), POINTER                   :: input
     249              : 
     250           44 :       CALL timeset(routineN, handle)
     251              : 
     252           44 :       t1 = m_walltime()
     253              : 
     254              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, input=input, &
     255           44 :                       qs_kind_set=qs_kind_set, cell=cell)
     256              : 
     257           44 :       NULLIFY (para_env_sub, blacs_env_sub)
     258              : 
     259           44 :       natom = bs_env%n_atom
     260              : 
     261              :       ! =========================================================================
     262              :       ! 1. SETUP DBCSR TOPOLOGY & EXACT OFFSETS
     263              :       !    mat_Z_lP inherits the grid row blocking (and row distribution) of
     264              :       !    mat_phi_mu_l; its columns are one block per RI atom.
     265              :       ! =========================================================================
     266           44 :       CALL dbcsr_get_info(mat_phi_mu_l, row_blk_size=r_blk_sizes, distribution=dist_phi)
     267           44 :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist_grid, npcols=npcol_phi)
     268              : 
     269           44 :       num_grid_chunks = SIZE(r_blk_sizes)
     270              : 
     271          132 :       ALLOCATE (row_offset(num_grid_chunks))
     272           44 :       row_offset(1) = 0
     273          602 :       DO i_blk = 2, num_grid_chunks
     274          602 :          row_offset(i_blk) = row_offset(i_blk - 1) + r_blk_sizes(i_blk - 1)
     275              :       END DO
     276              : 
     277          176 :       ALLOCATE (ri_blk_sizes(natom), col_dist_ri(natom))
     278          150 :       DO iatom = 1, natom
     279              :          ri_blk_sizes(iatom) = &
     280          106 :             bs_env%i_RI_end_from_atom(iatom) - bs_env%i_RI_start_from_atom(iatom) + 1
     281          150 :          col_dist_ri(iatom) = MOD(iatom - 1, npcol_phi)
     282              :       END DO
     283              : 
     284              :       CALL dbcsr_distribution_new(dist_Z, template=dist_phi, &
     285           44 :                                   row_dist=row_dist_grid, col_dist=col_dist_ri)
     286              : 
     287           44 :       IF (bs_env%ri_rs%Z_lP_exists) THEN
     288              :          CALL dbcsr_binary_read(filepath=TRIM(bs_env%prefix)//"Z_lP.matrix", &
     289              :                                 distribution=dist_Z, &
     290            2 :                                 matrix_new=mat_Z_lP)
     291            2 :          IF (bs_env%unit_nr > 0) THEN
     292              :             WRITE (bs_env%unit_nr, '(T2,A,T57,A,F7.1,A)') &
     293            1 :                'Read Z_lP from file ', ' Execution time', m_walltime() - t1, ' s'
     294              :             ! The grid rows use Morton order (spatial_atom_order). A Z_lP.matrix
     295              :             ! written with another grid ordering would be silently read into
     296              :             ! the current row order. Delete stale Z_lP.matrix files and recompute if in doubt.
     297              :             WRITE (bs_env%unit_nr, '(T2,A)') &
     298            1 :                '*** NOTE: Z_lP restart must match the current (spatial) grid row ordering ***'
     299            1 :             WRITE (bs_env%unit_nr, '(A)') ' '
     300              :          END IF
     301              :       ELSE
     302              : 
     303           42 :          IF (bs_env%unit_nr > 0) THEN
     304           21 :             WRITE (bs_env%unit_nr, '(A)') ' '
     305           21 :             WRITE (bs_env%unit_nr, '(T2,A)') 'Started computing Z_lP'
     306           21 :             CALL m_flush(bs_env%unit_nr)
     307              :          END IF
     308              :          CALL dbcsr_create(mat_Z_lP, name="mat_Z_lP", dist=dist_Z, &
     309              :                            matrix_type=dbcsr_type_no_symmetry, &
     310           42 :                            row_blk_size=r_blk_sizes, col_blk_size=ri_blk_sizes)
     311              : 
     312              :          ! Largest per-atom AO block, needed to size the 3c-integral work buffers.
     313           42 :          max_ao_size = 0
     314          142 :          DO j = 1, bs_env%n_atom
     315              :             max_ao_size = MAX(max_ao_size, &
     316              :                               bs_env%i_ao_end_from_atom(j) - &
     317          142 :                               bs_env%i_ao_start_from_atom(j) + 1)
     318              :          END DO
     319              : 
     320              :          ! Keep each path's integration domain: r_c + r_RI(P) for molecules,
     321              :          ! r_c + r_AO(P) for the Gamma image-summed approximation.
     322          126 :          ALLOCATE (cutoff_ri_per_atom(natom))
     323              : 
     324           42 :          r_c = bs_env%ri_metric%cutoff_radius
     325          142 :          DO iatom = 1, natom
     326          142 :             IF (periodic_images) THEN
     327            0 :                cutoff_ri_per_atom(iatom) = r_c + bs_env%ri_rs%radius_ao_per_atom(iatom)
     328              :             ELSE
     329          100 :                cutoff_ri_per_atom(iatom) = r_c + bs_env%ri_rs%radius_ri_per_atom(iatom)
     330              :             END IF
     331              :          END DO
     332              : 
     333           42 :          CALL print_sphere_cutoff_table(bs_env, cutoff_ri_per_atom)
     334              : 
     335              :          ! =========================================================================
     336              :          ! 2. PER-ATOM SOLVER CLASSIFICATION
     337              :          !    Split the atoms into "small" (single-rank LAPACK, Phase A) and "big"
     338              :          !    (distributed ScaLAPACK over subgroups of G ranks, Phase B) by comparing
     339              :          !    each atom's estimated solve peak memory against the measured budget.
     340              :          ! =========================================================================
     341              :          CALL classify_z_lp_atoms(bs_env, ri_rs_grid_points, cutoff_ri_per_atom, ri_blk_sizes, &
     342           42 :                                   n_local_grid_atom, small_list, n_small, big_list, n_big, G, periodic_images)
     343              : 
     344              :          ! LPT scheduling: sort the atoms of each phase by estimated solve cost
     345              :          ! (n_local_grid^3, Cholesky-dominated) and greedily assign to the least-loaded
     346              :          ! rank (Phase A) / subgroup (Phase B).
     347              :          CALL lpt_assign_atoms(small_list, n_small, n_local_grid_atom, para_env%num_pe, &
     348           42 :                                para_env%mepos, my_atoms_A)
     349           42 :          IF (n_big > 0) THEN
     350            0 :             n_groups = para_env%num_pe/G
     351            0 :             my_group = MIN(para_env%mepos/G, n_groups - 1)
     352              :             CALL lpt_assign_atoms(big_list, n_big, n_local_grid_atom, n_groups, my_group, &
     353            0 :                                   my_atoms_B)
     354              :          ELSE
     355           42 :             ALLOCATE (my_atoms_B(0))
     356              :          END IF
     357              : 
     358              :          ! Atoms this rank will process across both phases for rank-0 progress
     359           42 :          n_my_atoms = SIZE(my_atoms_A) + SIZE(my_atoms_B)
     360           42 :          n_done = 0
     361              : 
     362              :          ! Shared context for the three-center integrals (μν|P) of the RHS build
     363              :          CALL gw_3c_ctx_create(ctx_3c, bs_env, bs_env%ri_metric, &
     364              :                                basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
     365           42 :                                basis_i=bs_env%basis_set_RI)
     366              : 
     367              :          ! =========================================================================
     368              :          ! 3. TWO-PHASE LOOP OVER ATOMS
     369              :          !    Phase A processes the "small" atoms with the single-rank BLAS path
     370              :          !    Phase B processes the "big" atoms with the distributed ScaLAPACK path
     371              :          !    over rank subgroups of size G. phi_local for each atom's cutoff sphere
     372              :          !    is built on the fly to avoid replicating a global grid x AO matrix.
     373              :          ! =========================================================================
     374          126 :          DO iphase = 1, 2
     375           84 :             IF (iphase == 1) THEN
     376           42 :                use_dist = .FALSE.
     377           42 :                atom_j_mepos = 0
     378           42 :                atom_j_stride = 1
     379           42 :                phase_hi = SIZE(my_atoms_A)
     380              :             ELSE
     381           42 :                IF (n_big == 0) CYCLE
     382            0 :                use_dist = .TRUE.
     383            0 :                n_groups = para_env%num_pe/G
     384            0 :                my_group = MIN(para_env%mepos/G, n_groups - 1)
     385            0 :                ALLOCATE (para_env_sub)
     386            0 :                CALL para_env_sub%from_split(para_env, my_group)
     387            0 :                CALL cp_blacs_env_create(blacs_env=blacs_env_sub, para_env=para_env_sub)
     388            0 :                atom_j_mepos = para_env_sub%mepos
     389            0 :                atom_j_stride = para_env_sub%num_pe
     390              :                ! All ranks of a subgroup share my_group, hence the identical my_atoms_B list
     391              :                ! (the per-atom ScaLAPACK solve is collective over the subgroup).
     392            0 :                phase_hi = SIZE(my_atoms_B)
     393              :             END IF
     394              : 
     395           92 :             DO idx = 1, phase_hi
     396           50 :                item_start_time = m_walltime()
     397           50 :                IF (iphase == 1) THEN
     398           50 :                   atom_P = my_atoms_A(idx)
     399              :                ELSE
     400            0 :                   atom_P = my_atoms_B(idx)
     401              :                END IF
     402              : 
     403           50 :                n_loc_ri = ri_blk_sizes(atom_P)
     404           50 :                cutoff_ri = cutoff_ri_per_atom(atom_P)
     405              : 
     406              :                ! ---------------------------------------------------------------------
     407              :                ! A. Sphere-local AO matrix ϕ_μ(r_l): select the grid points with
     408              :                !    |r_l - R_P| <= cutoff_ri(P), using minimum-image distances for Gamma.
     409              :                !    The molecular path drops rows below EPS_FILTER; Gamma keeps all sphere rows.
     410              :                ! ---------------------------------------------------------------------
     411              :                CALL build_phi_on_sphere(bs_env, qs_kind_set, &
     412              :                                         ri_rs_grid_points, atom_P, cutoff_ri, &
     413              :                                         local_grid_idx, n_local_grid, phi_local, &
     414           50 :                                         ao_atoms, ao_col_start, periodic_images=periodic_images, ao_nonzero=ao_nonzero)
     415              : 
     416              :                ! ---------------------------------------------------------------------
     417              :                ! B. Right-hand side  d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P)
     418              :                ! ---------------------------------------------------------------------
     419          200 :                ALLOCATE (d_lp_local(n_local_grid, n_loc_ri))
     420           50 :                d_lp_local = 0.0_dp
     421              : 
     422              :                CALL compute_d_lp(bs_env, ctx_3c, phi_local, ao_atoms, ao_col_start, ao_nonzero, d_lp_local, n_local_grid, &
     423           50 :                                  n_loc_ri, atom_P, max_ao_size, atom_j_mepos, atom_j_stride, periodic_images)
     424              : 
     425           50 :                DEALLOCATE (ao_atoms, ao_col_start, ao_nonzero)
     426              : 
     427              :                ! Reduce per-subgroup-rank partials into the replicated d_lp_local.
     428              :                ! Skipped for BLAS path: each rank has the full sum locally.
     429           50 :                IF (use_dist) THEN
     430            0 :                   CALL para_env_sub%sum(d_lp_local)
     431              :                END IF
     432              : 
     433              :                ! ---------------------------------------------------------------------
     434              :                ! C. Solve the Jacobi-scaled, regularized fit with LAPACK or ScaLAPACK.
     435              :                ! ---------------------------------------------------------------------
     436              :                IF (use_dist) THEN
     437              :                   CALL solve_ri_rs_fit(phi_local, d_lp_local, bs_env%ri_rs%tikhonov, &
     438            0 :                                        para_env_sub, blacs_env_sub)
     439              :                ELSE
     440           50 :                   CALL solve_ri_rs_fit(phi_local, d_lp_local, bs_env%ri_rs%tikhonov)
     441              :                END IF
     442              : 
     443              :                ! ---------------------------------------------------------------------
     444              :                ! D. Scatter the solved Z columns back into the global sparse mat_Z_lP.
     445              :                ! ---------------------------------------------------------------------
     446           50 :                do_scatter = .TRUE.
     447           50 :                IF (use_dist) do_scatter = (para_env_sub%mepos == 0)
     448            0 :                IF (do_scatter) THEN
     449              :                   CALL store_Z_lP_columns(mat_Z_lP, d_lp_local, local_grid_idx, n_local_grid, &
     450              :                                           n_loc_ri, atom_P, r_blk_sizes, row_offset, &
     451           50 :                                           bs_env%eps_filter)
     452              :                END IF
     453              : 
     454           50 :                DEALLOCATE (d_lp_local)
     455           50 :                DEALLOCATE (local_grid_idx, phi_local)
     456              : 
     457              :                ! Report each atom completed by the printing rank. No inter-rank communication is
     458              :                ! needed; the final message below is printed only after the global synchronization.
     459           50 :                n_done = n_done + 1
     460              :                CALL print_Z_lP_progress(bs_env, n_done, n_my_atoms, &
     461           92 :                                         m_walltime() - item_start_time)
     462              :             END DO   ! idx: atoms of this phase owned by this rank / subgroup
     463              : 
     464              :             ! Tear down the Phase-B subgroup (all ranks created it collectively).
     465           84 :             IF (iphase == 2) THEN
     466            0 :                CALL cp_blacs_env_release(blacs_env_sub)
     467            0 :                CALL para_env_sub%free()
     468            0 :                DEALLOCATE (para_env_sub)
     469              :             END IF
     470              :          END DO   ! iphase
     471              : 
     472           42 :          DEALLOCATE (cutoff_ri_per_atom)
     473           42 :          DEALLOCATE (small_list, big_list)
     474              : 
     475           42 :          CALL gw_3c_ctx_release(ctx_3c)
     476              : 
     477           42 :          CALL dbcsr_finalize(mat_Z_lP)
     478              : 
     479           42 :          CALL para_env%sync()
     480              :          CALL print_Z_lP_progress(bs_env, natom, natom, m_walltime() - t1, &
     481           42 :                                   all_mpi_ranks=.TRUE.)
     482              : 
     483           42 :          logger => cp_get_default_logger()
     484              : 
     485           42 :          IF (BTEST(cp_print_key_should_output(logger%iter_info, input, key), cp_p_file)) THEN
     486            4 :             CALL dbcsr_binary_write(matrix=mat_Z_lP, filepath=TRIM(bs_env%prefix)//"Z_lP.matrix")
     487              :          END IF
     488              : 
     489              :       END IF
     490              : 
     491           44 :       DEALLOCATE (row_offset, ri_blk_sizes, col_dist_ri)
     492           44 :       CALL dbcsr_distribution_release(dist_Z)
     493              : 
     494           44 :       CALL timestop(handle)
     495              : 
     496          220 :    END SUBROUTINE compute_Z_lP_standard
     497              : 
     498              : ! **************************************************************************************************
     499              : !> \brief Prints the per-kind maximum RI-RS integration-sphere cutoff table.
     500              : !> \param bs_env ...
     501              : !> \param cutoff_ri_per_atom ...
     502              : ! **************************************************************************************************
     503           42 :    SUBROUTINE print_sphere_cutoff_table(bs_env, cutoff_ri_per_atom)
     504              : 
     505              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     506              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: cutoff_ri_per_atom
     507              : 
     508              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_sphere_cutoff_table'
     509              : 
     510              :       INTEGER                                            :: handle, iatom, ikind, nkind
     511           42 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cutoff_ri_per_kind
     512           42 :       TYPE(atomic_kind_type), DIMENSION(:), POINTER      :: atomic_kind_set
     513           42 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     514              : 
     515           42 :       CALL timeset(routineN, handle)
     516              : 
     517           42 :       atomic_kind_set => bs_env%ri_rs%atomic_kind_set
     518           42 :       particle_set => bs_env%ri_rs%particle_set
     519              : 
     520           42 :       IF (bs_env%unit_nr <= 0) THEN
     521           21 :          CALL timestop(handle)
     522              :          RETURN
     523              :       END IF
     524              : 
     525           21 :       nkind = SIZE(atomic_kind_set)
     526           63 :       ALLOCATE (cutoff_ri_per_kind(nkind))
     527           21 :       cutoff_ri_per_kind(:) = 0.0_dp
     528              : 
     529           71 :       DO iatom = 1, bs_env%n_atom
     530           50 :          ikind = particle_set(iatom)%atomic_kind%kind_number
     531           71 :          cutoff_ri_per_kind(ikind) = MAX(cutoff_ri_per_kind(ikind), cutoff_ri_per_atom(iatom))
     532              :       END DO
     533              : 
     534           21 :       WRITE (bs_env%unit_nr, '(T2,A)') 'Per-kind maximum RI-RS sphere cutoff (Å):'
     535           21 :       WRITE (bs_env%unit_nr, '(T4,A4,A14)') 'Kind', 'cutoff (Å)'
     536           50 :       DO ikind = 1, nkind
     537              :          WRITE (bs_env%unit_nr, '(T4,A4,F14.4)') &
     538           29 :             atomic_kind_set(ikind)%element_symbol, &
     539           79 :             cutoff_ri_per_kind(ikind)*angstrom
     540              :       END DO
     541           21 :       WRITE (bs_env%unit_nr, '(A)') ' '
     542              : 
     543           21 :       DEALLOCATE (cutoff_ri_per_kind)
     544              : 
     545           21 :       CALL timestop(handle)
     546              : 
     547           42 :    END SUBROUTINE print_sphere_cutoff_table
     548              : 
     549              : ! **************************************************************************************************
     550              : !> \brief LPT (longest-processing-time) assignment of the Z_lP atoms to workers (MPI ranks in
     551              : !>        Phase A, rank subgroups in Phase B): sort by estimated solve cost n_local_grid^3
     552              : !>        (the per-atom Cholesky dominates; the n^2 assembly terms order the atoms the same
     553              : !>        way) and greedily give each atom to the least-loaded worker.
     554              : !> \param atom_list ...
     555              : !> \param n_atoms ...
     556              : !> \param n_local_grid_atom ...
     557              : !> \param n_workers ...
     558              : !> \param my_worker ...
     559              : !> \param my_atoms ...
     560              : ! **************************************************************************************************
     561           42 :    SUBROUTINE lpt_assign_atoms(atom_list, n_atoms, n_local_grid_atom, n_workers, my_worker, &
     562              :                                my_atoms)
     563              : 
     564              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: atom_list
     565              :       INTEGER, INTENT(IN)                                :: n_atoms
     566              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: n_local_grid_atom
     567              :       INTEGER, INTENT(IN)                                :: n_workers, my_worker
     568              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: my_atoms
     569              : 
     570              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'lpt_assign_atoms'
     571              : 
     572              :       INTEGER                                            :: handle, i, iw, n_mine, w_min
     573           42 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: mine_tmp, perm
     574           42 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: cost, load
     575              : 
     576           42 :       CALL timeset(routineN, handle)
     577              : 
     578           42 :       IF (n_atoms <= 0) THEN
     579            0 :          ALLOCATE (my_atoms(0))
     580            0 :          CALL timestop(handle)
     581            0 :          RETURN
     582              :       END IF
     583              : 
     584          336 :       ALLOCATE (cost(n_atoms), perm(n_atoms), mine_tmp(n_atoms), load(n_workers))
     585          142 :       DO i = 1, n_atoms
     586          142 :          cost(i) = REAL(n_local_grid_atom(atom_list(i)), dp)**3
     587              :       END DO
     588           42 :       CALL sort(cost, n_atoms, perm)   ! ascending; walk backwards for largest-first
     589              : 
     590           42 :       load(:) = 0.0_dp
     591           42 :       n_mine = 0
     592          142 :       DO i = n_atoms, 1, -1
     593              :          w_min = 1
     594          200 :          DO iw = 2, n_workers
     595          200 :             IF (load(iw) < load(w_min)) w_min = iw
     596              :          END DO
     597          100 :          load(w_min) = load(w_min) + cost(i)
     598          142 :          IF (w_min - 1 == my_worker) THEN
     599           50 :             n_mine = n_mine + 1
     600           50 :             mine_tmp(n_mine) = atom_list(perm(i))
     601              :          END IF
     602              :       END DO
     603              : 
     604          126 :       ALLOCATE (my_atoms(n_mine))
     605           92 :       my_atoms(:) = mine_tmp(1:n_mine)
     606              : 
     607           42 :       CALL timestop(handle)
     608              : 
     609           84 :    END SUBROUTINE lpt_assign_atoms
     610              : 
     611              : ! **************************************************************************************************
     612              : !> \brief Computes the dense localized right-hand side for one RI atom P,
     613              : !>
     614              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P).
     615              : !>
     616              : !>        OMP-threaded over unordered AO-atom pairs: build (mu nu|P) in the reference cell,
     617              : !>        or sum (mu_R nu_S|P_0) over nearest images for Gamma. Contract grid-chunked pair
     618              : !>        densities into a thread-private d_lp partial, then reduce the partials into d_lp.
     619              : !>        The shared integral context caches Libint/spherical-transform tables; each thread
     620              : !>        owns its integral workspace. The kind-radius triangle screen in
     621              : !>        build_3c_integral_block_ctx skips negligible image triples via the screened output.
     622              : !> \param bs_env ...
     623              : !> \param ctx shared three-center integral context
     624              : !> \param phi_val AO values on local-sphere grid rows and compact active-AO columns
     625              : !> \param ao_atoms global indices of the active AO atoms
     626              : !> \param ao_col_start local phi column starts, including the terminal column offset
     627              : !> \param ao_nonzero exact nonzero AO support on the retained grid rows
     628              : !> \param d_lp RHS (n_grid_total × n_loc_ri), zeroed by the caller and accumulated here
     629              : !> \param n_grid_total number of retained local-sphere grid rows
     630              : !> \param n_loc_ri number of RI functions on atom_P
     631              : !> \param atom_P RI atom fixed in the reference cell (0,0,0)
     632              : !> \param max_ao_size ...
     633              : !> \param atom_j_mepos ...
     634              : !> \param atom_j_stride ...
     635              : !> \param periodic_images sum nearest AO images with RI atom P fixed in the reference cell
     636              : ! **************************************************************************************************
     637           50 :    SUBROUTINE compute_d_lp(bs_env, ctx, phi_val, ao_atoms, ao_col_start, ao_nonzero, d_lp, n_grid_total, n_loc_ri, atom_P, &
     638              :                            max_ao_size, atom_j_mepos, atom_j_stride, periodic_images)
     639              : 
     640              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     641              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
     642              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: phi_val
     643              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_atoms, ao_col_start
     644              :       LOGICAL, DIMENSION(:, :), INTENT(IN)               :: ao_nonzero
     645              :       INTEGER, INTENT(IN)                                :: n_grid_total, n_loc_ri
     646              :       REAL(KIND=dp), INTENT(INOUT)                       :: d_lp(n_grid_total, n_loc_ri)
     647              :       INTEGER, INTENT(IN)                                :: atom_P, max_ao_size, atom_j_mepos, &
     648              :                                                             atom_j_stride
     649              :       LOGICAL, INTENT(IN)                                :: periodic_images
     650              : 
     651              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'compute_d_lp'
     652              :       INTEGER, PARAMETER                                 :: grid_chunk = 1024
     653              : 
     654              :       INTEGER :: atom_j, atom_k, c, cell_R_vec(3), cell_S_vec(3), handle, handle2, &
     655              :          image_extent(3), ix_R, ix_S, iy_R, iy_S, iz_R, iz_S, j, jj, jk_idx, jsize, jstart, k, kk, &
     656              :          ksize, kstart, l, l0, n_grid_pair, point, ri
     657           50 :       INTEGER, ALLOCATABLE                               :: grid_index(:)
     658              :       LOGICAL                                            :: any_kept, screened
     659              :       REAL(KIND=dp)                                      :: pair_factor
     660           50 :       REAL(KIND=dp), ALLOCATABLE                         :: grid_result(:, :), int_3c_prv(:, :, :)
     661           50 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_prv, int_2d_prv, rho_chunk
     662           50 :       TYPE(gw_3c_ws_type)                                :: ws
     663              : 
     664           50 :       CALL timeset(routineN, handle)
     665           50 :       image_extent = 0
     666           50 :       IF (periodic_images) image_extent = bs_env%ri_rs%cell%perd
     667              : 
     668              :       !$OMP PARALLEL DEFAULT(NONE) &
     669              :       !$OMP SHARED(bs_env, ctx, phi_val, ao_atoms, ao_col_start, ao_nonzero, d_lp, &
     670              :       !$OMP        n_grid_total, n_loc_ri, atom_P, max_ao_size, &
     671              :       !$OMP        atom_j_mepos, atom_j_stride, image_extent) &
     672              :       !$OMP PRIVATE(grid_index, grid_result, n_grid_pair, point, pair_factor, &
     673              :       !$OMP         jj, kk, atom_j, atom_k, c, handle2, j, jk_idx, jsize, jstart, k, ksize, kstart, &
     674              :       !$OMP         l, l0, ri, screened, d_lp_prv, int_2d_prv, rho_chunk, int_3c_prv, ws, &
     675           50 :       !$OMP         any_kept, ix_R, iy_R, iz_R, ix_S, iy_S, iz_S, cell_R_vec, cell_S_vec)
     676              : 
     677              :       ! The integral context is shared; all scratch arrays and the accumulating RHS are thread-private.
     678              :       CALL gw_3c_ws_create(ws, ctx)
     679              :       ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, n_loc_ri))
     680              :       ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, n_loc_ri))
     681              :       ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
     682              :       ALLOCATE (d_lp_prv(n_grid_total, n_loc_ri))
     683              :       ALLOCATE (grid_index(n_grid_total), grid_result(grid_chunk, n_loc_ri))
     684              :       d_lp_prv(:, :) = 0.0_dp
     685              : 
     686              :       ! MPI assigns each unordered pair through its first atom; OpenMP divides those atoms.
     687              :       ! Skip only exact-zero pair-grid support, without a new screening threshold.
     688              :       !$OMP DO SCHEDULE(DYNAMIC)
     689              :       DO jj = 1, SIZE(ao_atoms)
     690              :          atom_j = ao_atoms(jj)
     691              :          IF (MOD(atom_j - 1, atom_j_stride) /= atom_j_mepos) CYCLE
     692              :          DO kk = jj, SIZE(ao_atoms)
     693              :             atom_k = ao_atoms(kk)
     694              :             jstart = ao_col_start(jj)
     695              :             kstart = ao_col_start(kk)
     696              :             jsize = ao_col_start(jj + 1) - jstart
     697              :             ksize = ao_col_start(kk + 1) - kstart
     698              : 
     699              :             n_grid_pair = 0
     700              :             DO point = 1, n_grid_total
     701              :                IF (.NOT. (ao_nonzero(point, jj) .AND. ao_nonzero(point, kk))) CYCLE
     702              :                n_grid_pair = n_grid_pair + 1
     703              :                grid_index(n_grid_pair) = point
     704              :             END DO
     705              :             IF (n_grid_pair == 0) CYCLE
     706              :             ! (μν|P) = (νμ|P): distinct atom pairs contribute twice.
     707              :             pair_factor = 1.0_dp
     708              :             IF (atom_j /= atom_k) pair_factor = 2.0_dp
     709              : 
     710              :             ! For Gamma, sum (mu_R nu_S|P_0); the molecular path has only R=S=0.
     711              :             ! AO exchange symmetry applies to the complete image sum, so the same
     712              :             ! unordered atom pairs and pair_factor are used in both paths.
     713              :             ! The integral builder accumulates in place, including across image triples.
     714              :             ! Its kind-radius triangle screen rejects image triples beyond the integral cutoff.
     715              :             int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri) = 0.0_dp
     716              :             any_kept = .FALSE.
     717              :             DO ix_R = -image_extent(1), image_extent(1)
     718              :             DO iy_R = -image_extent(2), image_extent(2)
     719              :             DO iz_R = -image_extent(3), image_extent(3)
     720              :                cell_R_vec = [ix_R, iy_R, iz_R]
     721              :                DO ix_S = -image_extent(1), image_extent(1)
     722              :                DO iy_S = -image_extent(2), image_extent(2)
     723              :                DO iz_S = -image_extent(3), image_extent(3)
     724              :                   cell_S_vec = [ix_S, iy_S, iz_S]
     725              :                   CALL build_3c_integral_block_ctx(int_3c_prv(1:jsize, 1:ksize, 1:n_loc_ri), &
     726              :                                                    ctx, ws, atom_j=atom_j, atom_k=atom_k, atom_i=atom_P, &
     727              :                                                    cell_j=cell_R_vec, cell_k=cell_S_vec, screened=screened)
     728              :                   IF (screened) CYCLE
     729              :                   any_kept = .TRUE.
     730              :                END DO
     731              :                END DO
     732              :                END DO
     733              :             END DO
     734              :             END DO
     735              :             END DO
     736              :             IF (.NOT. any_kept) CYCLE
     737              : 
     738              :             ! Flatten (μν|P) to a matrix with combined AO index (ν-1)*jsize + μ for BLAS.
     739              :             DO ri = 1, n_loc_ri
     740              :                DO k = 1, ksize
     741              :                   DO j = 1, jsize
     742              :                      jk_idx = (k - 1)*jsize + j
     743              :                      int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
     744              :                   END DO
     745              :                END DO
     746              :             END DO
     747              : 
     748              :             ! Pair density ρ(l, μν) = ϕ_μ(r_l) ϕ_ν(r_l) in grid chunks, contracted on the fly:
     749              :             ! d_lP += Σ_μν ρ(l, μν) (μν|P), with the atom-pair multiplicity included in pair_factor.
     750              :             DO l0 = 1, n_grid_pair, grid_chunk
     751              :                c = MIN(grid_chunk, n_grid_pair - l0 + 1)
     752              :                DO k = 1, ksize
     753              :                   DO j = 1, jsize
     754              :                      jk_idx = (k - 1)*jsize + j
     755              :                      DO l = 1, c
     756              :                         point = grid_index(l0 + l - 1)
     757              :                         rho_chunk(l, jk_idx) = phi_val(point, jstart + j - 1)* &
     758              :                                                phi_val(point, kstart + k - 1)
     759              :                      END DO
     760              :                   END DO
     761              :                END DO
     762              :                CALL timeset(routineN//"_dgemm", handle2)
     763              :                CALL dgemm("N", "N", c, n_loc_ri, jsize*ksize, &
     764              :                           pair_factor, rho_chunk, grid_chunk, &
     765              :                           int_2d_prv, max_ao_size*max_ao_size, &
     766              :                           0.0_dp, grid_result, grid_chunk)
     767              :                DO ri = 1, n_loc_ri
     768              :                   DO l = 1, c
     769              :                      point = grid_index(l0 + l - 1)
     770              :                      d_lp_prv(point, ri) = d_lp_prv(point, ri) + grid_result(l, ri)
     771              :                   END DO
     772              :                END DO
     773              :                CALL timestop(handle2)
     774              :             END DO
     775              :          END DO
     776              :       END DO
     777              :       !$OMP END DO
     778              : 
     779              :       ! Reduce once per thread; distributed fits subsequently sum the rank partials in the subgroup.
     780              :       !$OMP CRITICAL (compute_d_lp_reduce)
     781              :       d_lp(1:n_grid_total, 1:n_loc_ri) = d_lp(1:n_grid_total, 1:n_loc_ri) + &
     782              :                                          d_lp_prv(1:n_grid_total, 1:n_loc_ri)
     783              :       !$OMP END CRITICAL (compute_d_lp_reduce)
     784              : 
     785              :       DEALLOCATE (int_3c_prv, int_2d_prv, rho_chunk, d_lp_prv, grid_index, grid_result)
     786              :       CALL gw_3c_ws_release(ws)
     787              : 
     788              :       !$OMP END PARALLEL
     789              : 
     790           50 :       CALL timestop(handle)
     791              : 
     792           50 :    END SUBROUTINE compute_d_lp
     793              : 
     794              : ! **************************************************************************************************
     795              : !> \brief Computes the RI-RS matrix Z_lP for an automatically optimized RI basis.
     796              : !>
     797              : !>        The fitting coefficients Z_lP satisfy
     798              : !>
     799              : !>          Σ_l' D_ll' Z_l'P = d_lP,
     800              : !>          D_ll' = [Σ_μ ϕ_μ(r_l) ϕ_μ(r_l')]²,
     801              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P).
     802              : !>
     803              : !>        For the standard RI-RS fit, P is an atom-centered RI function. Here an optimized
     804              : !>        function may contain reference functions on neighboring atoms,
     805              : !>
     806              : !>          φ_p(r) = Σ_A Σ_{P∈A} U_Pp^A φ_P^A(r),
     807              : !>
     808              : !>        so all atomic contributions to d_lp must be accumulated before solving for Z_lp.
     809              : !>        If every optimized column uses the complete RI-RS grid, all columns are solved in one
     810              : !>        system. Otherwise, each atom-blocked column set is fitted on its own integration sphere;
     811              : !>        grid points outside that sphere are excluded from its fitting equations.
     812              : !> \param qs_env ...
     813              : !> \param bs_env ...
     814              : !> \param ri_rs_grid_points ...
     815              : !> \param mat_phi_mu_l ...
     816              : !> \param mat_Z_lP ...
     817              : ! **************************************************************************************************
     818            4 :    SUBROUTINE compute_Z_lP_auto_ri(qs_env, bs_env, ri_rs_grid_points, mat_phi_mu_l, mat_Z_lP)
     819              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     820              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     821              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
     822              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_phi_mu_l
     823              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
     824              : 
     825              :       CHARACTER(LEN=*), PARAMETER :: key = 'PROPERTIES%BANDSTRUCTURE%GW%PRINT%RESTART', &
     826              :          routineN = 'compute_Z_lP_auto_ri'
     827              : 
     828              :       INTEGER :: AB_block, atom_A, atom_B, block_size_b, column_first, first_p_AB, fit_atom, &
     829              :          handle, last_atom, max_ao_size, max_nRI_ref, mypcol, myprow, n_active, n_candidate, &
     830              :          n_done, n_my_atoms, n_to_a, natom, ncol, ngrid, nri, nRI_ref_A, nRI_ref_B, output_offset, &
     831              :          ri_atom, slot
     832            4 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: ao_atoms, ao_col_start, candidate_order, &
     833            8 :                                                             candidates, local_grid_idx, ri_atoms, &
     834            4 :                                                             row_offset
     835            8 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist_ri, ri_blk_sizes, &
     836            4 :                                                             row_dist_grid, row_size_grid
     837              :       LOGICAL                                            :: AB_block_local, common_grid_available, &
     838              :                                                             have_fitted_columns, &
     839              :                                                             reuse_atomic_integrals
     840            4 :       LOGICAL, ALLOCATABLE                               :: ao_nonzero(:, :)
     841              :       REAL(KIND=dp)                                      :: cutoff_ri, item_start_time, &
     842              :                                                             mem_avail_GB, previous_fit_peak, r_c, &
     843              :                                                             t1
     844            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_all, d_lp_local, phi_local, U_Pp_AB
     845            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: U_active
     846              :       REAL(KIND=dp), DIMENSION(3)                        :: center
     847              :       TYPE(cell_type), POINTER                           :: cell
     848              :       TYPE(cp_logger_type), POINTER                      :: logger
     849              :       TYPE(dbcsr_distribution_type)                      :: dist_Z
     850              :       TYPE(dbcsr_type)                                   :: mat_rhs
     851           52 :       TYPE(gw_3c_ctx_type)                               :: ctx_3c
     852              :       TYPE(mp_para_env_type), POINTER                    :: para_env, para_env_col
     853            4 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
     854            4 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
     855              :       TYPE(section_vals_type), POINTER                   :: input
     856              : 
     857            4 :       CALL timeset(routineN, handle)
     858            4 :       t1 = m_walltime()
     859            4 :       IF (bs_env%unit_nr > 0) THEN
     860            2 :          WRITE (bs_env%unit_nr, '(A)') ' '
     861            2 :          WRITE (bs_env%unit_nr, '(T2,A)') 'Started computing Z_lP'
     862            2 :          CALL m_flush(bs_env%unit_nr)
     863              :       END IF
     864              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, input=input, &
     865            4 :                       qs_kind_set=qs_kind_set, cell=cell)
     866            4 :       NULLIFY (para_env_col)
     867              : 
     868            4 :       natom = bs_env%n_atom
     869            4 :       CPASSERT(bs_env%auto_ri%AB_block_count > 0)
     870            4 :       CPASSERT(SIZE(particle_set) == natom)
     871              : 
     872              :       CALL prepare_Z_lP_auto_ri(bs_env, mat_phi_mu_l, mat_Z_lP, dist_Z, row_size_grid, &
     873              :                                 row_dist_grid, col_dist_ri, ri_blk_sizes, row_offset, &
     874            4 :                                 myprow, mypcol, max_ao_size)
     875              : 
     876              :       CALL gw_3c_ctx_create(ctx_3c, bs_env, bs_env%ri_metric, &
     877              :                             basis_j=bs_env%basis_set_AO, basis_k=bs_env%basis_set_AO, &
     878            4 :                             basis_i=bs_env%basis_set_RI)
     879              : 
     880              :       ! Check whether one grid can represent d_lp for every optimized atomic column block.
     881              :       CALL common_Z_lP_grid_available(bs_env, ri_rs_grid_points, &
     882            4 :                                       common_grid_available, have_fitted_columns)
     883              : 
     884            4 :       IF (common_grid_available .AND. have_fitted_columns) THEN
     885            4 :          CALL mp_mem_avail_per_rank_GB(para_env, mem_avail_GB)
     886              :          ! Evaluate ϕ_μ(r_l) once on the common grid.
     887              :          CALL build_phi_on_complete_grid(bs_env, qs_kind_set, &
     888              :                                          ri_rs_grid_points, local_grid_idx, ngrid, &
     889            4 :                                          phi_local, ao_atoms, ao_col_start, ao_nonzero)
     890           16 :          nri = SUM(ri_blk_sizes)
     891            4 :          CALL record_fit_memory(bs_env%ri_rs, ngrid, SIZE(phi_local, 2), nri)
     892            4 :          CALL print_fit_memory_estimate(bs_env, mem_avail_GB)
     893           16 :          ALLOCATE (d_lp_all(ngrid, nri), source=0.0_dp)
     894              :          ! d_lp = Σ_A Σ_{P∈A} d_lP U_Pp for every optimized RI function q.
     895              :          CALL compute_d_lp_auto_ri_batch(bs_env, ctx_3c, phi_local, ao_atoms, ao_col_start, ao_nonzero, d_lp_all, ngrid, &
     896            4 :                                          max_ao_size, para_env%mepos, para_env%num_pe)
     897            4 :          DEALLOCATE (ao_atoms, ao_col_start, ao_nonzero)
     898            4 :          CALL para_env%sum(d_lp_all)
     899            4 :          CALL solve_ri_rs_fit(phi_local, d_lp_all, bs_env%ri_rs%tikhonov)
     900              : 
     901           22 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
     902           18 :             atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
     903           18 :             atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
     904           18 :             AB_block_local = col_dist_ri(atom_A) == mypcol
     905           18 :             IF (atom_B /= atom_A) AB_block_local = AB_block_local .OR. col_dist_ri(atom_B) == mypcol
     906           12 :             IF (.NOT. AB_block_local) CYCLE
     907           18 :             ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block)
     908           18 :             n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
     909           18 :             IF (n_to_a > 0 .AND. col_dist_ri(atom_A) == mypcol) THEN
     910              :                output_offset = SUM(ri_blk_sizes(:atom_A - 1)) + &
     911           28 :                                bs_env%auto_ri%AB_first_p_A(AB_block) - 1
     912              :                CALL add_Z_lP_columns(mat_Z_lP, &
     913              :                                      d_lp_all(:, output_offset + 1:output_offset + n_to_a), &
     914              :                                      local_grid_idx, ngrid, atom_A, &
     915              :                                      bs_env%auto_ri%AB_first_p_A(AB_block), &
     916              :                                      ri_blk_sizes(atom_A), row_size_grid, row_offset, &
     917              :                                      row_dist_grid, &
     918           14 :                                      myprow, bs_env%eps_filter)
     919              :             END IF
     920           22 :             IF (n_to_a < ncol) THEN
     921            4 :                CPASSERT(atom_B /= atom_A)
     922            4 :                IF (col_dist_ri(atom_B) == mypcol) THEN
     923            4 :                   block_size_b = ri_blk_sizes(atom_B)
     924              :                   output_offset = SUM(ri_blk_sizes(:atom_B - 1)) + &
     925           10 :                                   bs_env%auto_ri%AB_first_p_B(AB_block) - 1
     926              :                   CALL add_Z_lP_columns(mat_Z_lP, &
     927              :                                         d_lp_all(:, output_offset + 1: &
     928              :                                                  output_offset + ncol - n_to_a), &
     929              :                                         local_grid_idx, ngrid, atom_B, &
     930              :                                         bs_env%auto_ri%AB_first_p_B(AB_block), block_size_b, &
     931              :                                         row_size_grid, row_offset, row_dist_grid, myprow, &
     932            4 :                                         bs_env%eps_filter)
     933              :                END IF
     934              :             END IF
     935              :          END DO
     936            4 :          DEALLOCATE (d_lp_all, local_grid_idx, phi_local)
     937              :       ELSE
     938              :          reuse_atomic_integrals = &
     939              :             bs_env%ri_rs%cutoff_radius_ri_ao <= 0.0_dp .OR. &
     940              :             bs_env%ri_rs%cutoff_radius_ri_ao <= &
     941            0 :             MINVAL(bs_env%ri_rs%radius_ao_per_atom)
     942              :          IF (reuse_atomic_integrals) THEN
     943              :             ! Form d_lp = Σ_A Σ_{P∈A} d_lP U_Pp before fitting each atomic column block.
     944              :             CALL compute_auto_ri_d_lp(qs_env, bs_env, ctx_3c, &
     945              :                                       ri_rs_grid_points, mat_phi_mu_l, mat_rhs, &
     946            0 :                                       max_ao_size)
     947            0 :             CALL dbcsr_release(mat_Z_lP)
     948              :             CALL dbcsr_create(mat_Z_lP, name='mat_Z_lP localized AA/AB', dist=dist_Z, &
     949              :                               matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size_grid, &
     950            0 :                               col_blk_size=ri_blk_sizes)
     951              :             ! Solve Σ_l' D_ll' Z_l'q = d_lp on each atomic fitting grid.
     952            0 :             CALL fit_auto_ri_z_lp(qs_env, bs_env, ri_rs_grid_points, mat_rhs, mat_Z_lP)
     953            0 :             CALL dbcsr_release(mat_rhs)
     954              :          ELSE
     955              :             ! All ranks query memory before process columns enter independent fitting loops.
     956            0 :             CALL mp_mem_avail_per_rank_GB(para_env, mem_avail_GB)
     957            0 :             ALLOCATE (para_env_col)
     958            0 :             CALL para_env_col%from_split(para_env, mypcol)
     959            0 :             n_my_atoms = 0
     960            0 :             DO fit_atom = 1, natom
     961            0 :                IF (col_dist_ri(fit_atom) /= mypcol) CYCLE
     962            0 :                IF (ri_blk_sizes(fit_atom) == 0) CYCLE
     963            0 :                n_my_atoms = n_my_atoms + 1
     964              :             END DO
     965            0 :             n_done = 0
     966              :             ALLOCATE (candidates(2*bs_env%auto_ri%AB_block_count), &
     967            0 :                       candidate_order(2*bs_env%auto_ri%AB_block_count))
     968            0 :             DO fit_atom = 1, natom
     969            0 :                IF (col_dist_ri(fit_atom) /= mypcol) CYCLE
     970            0 :                ncol = ri_blk_sizes(fit_atom)
     971            0 :                IF (ncol == 0) CYCLE
     972            0 :                item_start_time = m_walltime()
     973              :                ! Only reference RI atoms contributing to this fitted column block are stored.
     974            0 :                n_candidate = 0
     975            0 :                DO AB_block = 1, bs_env%auto_ri%AB_block_count
     976            0 :                   atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
     977            0 :                   atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
     978            0 :                   n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
     979            0 :                   IF (.NOT. ((fit_atom == atom_A .AND. n_to_a > 0) .OR. &
     980              :                              (fit_atom == atom_B .AND. &
     981              :                               n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block)))) CYCLE
     982            0 :                   n_candidate = n_candidate + 1
     983            0 :                   candidates(n_candidate) = atom_A
     984            0 :                   IF (atom_B == atom_A) CYCLE
     985            0 :                   n_candidate = n_candidate + 1
     986            0 :                   candidates(n_candidate) = atom_B
     987              :                END DO
     988            0 :                CPASSERT(n_candidate > 0)
     989            0 :                CALL sort(candidates, n_candidate, candidate_order)
     990            0 :                n_active = 0
     991            0 :                last_atom = 0
     992            0 :                DO slot = 1, n_candidate
     993            0 :                   ri_atom = candidates(slot)
     994            0 :                   IF (ri_atom == last_atom) CYCLE
     995            0 :                   n_active = n_active + 1
     996            0 :                   candidates(n_active) = ri_atom
     997            0 :                   last_atom = ri_atom
     998              :                END DO
     999            0 :                ALLOCATE (ri_atoms(n_active), source=candidates(:n_active))
    1000              :                max_nRI_ref = 0
    1001            0 :                DO slot = 1, n_active
    1002            0 :                   max_nRI_ref = MAX(max_nRI_ref, get_ref_RI_size(bs_env, ri_atoms(slot)))
    1003              :                END DO
    1004            0 :                ALLOCATE (U_active(max_nRI_ref, ncol, n_active), source=0.0_dp)
    1005              : 
    1006            0 :                DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1007            0 :                   atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1008            0 :                   atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1009            0 :                   n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1010            0 :                   IF (fit_atom == atom_A .AND. n_to_a > 0) THEN
    1011            0 :                      column_first = bs_env%auto_ri%AB_first_p_A(AB_block)
    1012            0 :                      first_p_AB = 1
    1013            0 :                      ncol = n_to_a
    1014            0 :                   ELSE IF (fit_atom == atom_B .AND. &
    1015              :                            n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block)) THEN
    1016            0 :                      column_first = bs_env%auto_ri%AB_first_p_B(AB_block)
    1017            0 :                      first_p_AB = n_to_a + 1
    1018            0 :                      ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block) - n_to_a
    1019              :                   ELSE
    1020              :                      CYCLE
    1021              :                   END IF
    1022            0 :                   CALL get_U_Pp_AB(bs_env%auto_ri, AB_block, U_Pp_AB)
    1023              : 
    1024            0 :                   slot = locate(ri_atoms, atom_A)
    1025            0 :                   nRI_ref_A = get_ref_RI_size(bs_env, atom_A)
    1026              :                   U_active(1:nRI_ref_A, &
    1027              :                            column_first:column_first + ncol - 1, slot) = &
    1028              :                      U_Pp_AB( &
    1029            0 :                      1:nRI_ref_A, first_p_AB:first_p_AB + ncol - 1)
    1030            0 :                   IF (atom_B /= atom_A) THEN
    1031            0 :                      slot = locate(ri_atoms, atom_B)
    1032            0 :                      nRI_ref_B = get_ref_RI_size(bs_env, atom_B)
    1033              :                      U_active(1:nRI_ref_B, &
    1034              :                               column_first:column_first + ncol - 1, slot) = &
    1035              :                         U_Pp_AB( &
    1036              :                         nRI_ref_A + 1:nRI_ref_A + nRI_ref_B, &
    1037            0 :                         first_p_AB:first_p_AB + ncol - 1)
    1038              :                   END IF
    1039            0 :                   DEALLOCATE (U_Pp_AB)
    1040              :                END DO
    1041              : 
    1042            0 :                center = particle_set(fit_atom)%r
    1043            0 :                r_c = bs_env%ri_metric%cutoff_radius
    1044            0 :                cutoff_ri = 0.0_dp
    1045            0 :                DO slot = 1, n_active
    1046            0 :                   ri_atom = ri_atoms(slot)
    1047              :                   cutoff_ri = MAX(cutoff_ri, &
    1048              :                                   r_c + bs_env%ri_rs%radius_ri_per_atom(ri_atom) + &
    1049            0 :                                   NORM2(center - particle_set(ri_atom)%r))
    1050              :                END DO
    1051              :                CALL build_phi_on_sphere(bs_env, qs_kind_set, &
    1052              :                                         ri_rs_grid_points, fit_atom, cutoff_ri, &
    1053              :                                         local_grid_idx, ngrid, phi_local, ao_atoms, ao_col_start, &
    1054            0 :                                         center=center, ao_nonzero=ao_nonzero)
    1055            0 :                ncol = ri_blk_sizes(fit_atom)
    1056            0 :                previous_fit_peak = bs_env%ri_rs%fit_peak_bytes
    1057            0 :                CALL record_fit_memory(bs_env%ri_rs, ngrid, SIZE(phi_local, 2), ncol)
    1058            0 :                IF (bs_env%ri_rs%fit_peak_bytes > previous_fit_peak) THEN
    1059            0 :                   CALL print_fit_memory_estimate(bs_env, mem_avail_GB)
    1060              :                END IF
    1061            0 :                ALLOCATE (d_lp_local(ngrid, ncol), source=0.0_dp)
    1062              :                ! d_lp = Σ_A Σ_{P∈A} d_lP U_Pp on the grid of this atomic column block.
    1063              :                CALL compute_d_lp_auto_ri(bs_env, ctx_3c, phi_local, ao_atoms, ao_col_start, ao_nonzero, &
    1064              :                                          d_lp_local, ri_atoms, U_active, max_ao_size, &
    1065            0 :                                          para_env_col%mepos, para_env_col%num_pe)
    1066            0 :                DEALLOCATE (ao_atoms, ao_col_start, ao_nonzero)
    1067            0 :                CALL para_env_col%sum(d_lp_local)
    1068            0 :                CALL solve_ri_rs_fit(phi_local, d_lp_local, bs_env%ri_rs%tikhonov)
    1069              :                CALL add_Z_lP_columns(mat_Z_lP, d_lp_local, local_grid_idx, ngrid, fit_atom, 1, &
    1070              :                                      ri_blk_sizes(fit_atom), row_size_grid, row_offset, &
    1071              :                                      row_dist_grid, &
    1072            0 :                                      myprow, bs_env%eps_filter)
    1073            0 :                DEALLOCATE (d_lp_local, local_grid_idx, phi_local)
    1074            0 :                DEALLOCATE (U_active, ri_atoms)
    1075            0 :                n_done = n_done + 1
    1076              :                CALL print_Z_lP_progress(bs_env, n_done, n_my_atoms, &
    1077            0 :                                         m_walltime() - item_start_time)
    1078              :             END DO
    1079            0 :             DEALLOCATE (candidates, candidate_order)
    1080            0 :             CALL para_env_col%free()
    1081            0 :             DEALLOCATE (para_env_col)
    1082              :          END IF
    1083              :       END IF
    1084            4 :       CALL gw_3c_ctx_release(ctx_3c)
    1085              : 
    1086            4 :       CALL dbcsr_filter(mat_Z_lP, bs_env%eps_filter)
    1087            4 :       CALL dbcsr_finalize(mat_Z_lP)
    1088            4 :       CALL para_env%sync()
    1089              :       CALL print_Z_lP_progress(bs_env, natom, natom, m_walltime() - t1, &
    1090            4 :                                all_mpi_ranks=.TRUE.)
    1091            4 :       logger => cp_get_default_logger()
    1092            4 :       IF (BTEST(cp_print_key_should_output(logger%iter_info, input, key), cp_p_file)) THEN
    1093            0 :          CALL dbcsr_binary_write(matrix=mat_Z_lP, filepath=TRIM(bs_env%prefix)//'Z_lP.matrix')
    1094              :       END IF
    1095              : 
    1096            4 :       DEALLOCATE (row_offset, ri_blk_sizes, col_dist_ri)
    1097            4 :       CALL dbcsr_distribution_release(dist_Z)
    1098            4 :       CALL timestop(handle)
    1099              : 
    1100           12 :    END SUBROUTINE compute_Z_lP_auto_ri
    1101              : 
    1102              : ! **************************************************************************************************
    1103              : !> \brief Prints completed Z_lP atoms and execution time from the printing rank.
    1104              : !> \param bs_env ...
    1105              : !> \param n_done Number of atoms completed by the printing rank.
    1106              : !> \param n_total Total number of atoms assigned to the printing rank.
    1107              : !> \param execution_time Wall-clock time used for the reported work.
    1108              : !> \param all_mpi_ranks Whether all MPI ranks have completed the calculation.
    1109              : ! **************************************************************************************************
    1110          102 :    SUBROUTINE print_Z_lP_progress(bs_env, n_done, n_total, execution_time, all_mpi_ranks)
    1111              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1112              :       INTEGER, INTENT(IN)                                :: n_done, n_total
    1113              :       REAL(KIND=dp), INTENT(IN)                          :: execution_time
    1114              :       LOGICAL, INTENT(IN), OPTIONAL                      :: all_mpi_ranks
    1115              : 
    1116              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'print_Z_lP_progress'
    1117              : 
    1118              :       INTEGER                                            :: handle
    1119              :       LOGICAL                                            :: completed
    1120              : 
    1121          102 :       CALL timeset(routineN, handle)
    1122              : 
    1123          102 :       IF (bs_env%unit_nr > 0) THEN
    1124           56 :          completed = .FALSE.
    1125           56 :          IF (PRESENT(all_mpi_ranks)) completed = all_mpi_ranks
    1126           23 :          IF (completed) THEN
    1127              :             WRITE (bs_env%unit_nr, '(T2,A,T58,A,F7.1,A,/)') &
    1128           23 :                'Computed Z_lP (all MPI ranks) for all atoms,', &
    1129           46 :                'Execution time', execution_time, ' s'
    1130              :          ELSE
    1131              :             WRITE (bs_env%unit_nr, '(T2,A,I11,A,I3,A,F7.1,A)') &
    1132           33 :                'Computed Z_lP (MPI rank 0) for atom', n_done, ' /', n_total, &
    1133           66 :                ',    Execution time', execution_time, ' s'
    1134              :          END IF
    1135           56 :          CALL m_flush(bs_env%unit_nr)
    1136              :       END IF
    1137              : 
    1138          102 :       CALL timestop(handle)
    1139              : 
    1140          102 :    END SUBROUTINE print_Z_lP_progress
    1141              : 
    1142              : ! **************************************************************************************************
    1143              : !> \brief Prepares the distributed Z_lP matrix and its atom-blocked column layout. Column block A
    1144              : !>        contains all optimized functions assigned to atom A. Every local block is reserved once
    1145              : !>        because several AA/AB contraction blocks may contribute to the same block.
    1146              : !> \param bs_env ...
    1147              : !> \param mat_phi_mu_l ...
    1148              : !> \param mat_Z_lP ...
    1149              : !> \param dist_Z ...
    1150              : !> \param row_size_grid ...
    1151              : !> \param row_dist_grid ...
    1152              : !> \param col_dist_ri ...
    1153              : !> \param ri_blk_sizes ...
    1154              : !> \param row_offset ...
    1155              : !> \param myprow ...
    1156              : !> \param mypcol ...
    1157              : !> \param max_ao_size ...
    1158              : ! **************************************************************************************************
    1159            4 :    SUBROUTINE prepare_Z_lP_auto_ri(bs_env, mat_phi_mu_l, mat_Z_lP, dist_Z, row_size_grid, &
    1160              :                                    row_dist_grid, col_dist_ri, ri_blk_sizes, row_offset, &
    1161              :                                    myprow, mypcol, max_ao_size)
    1162              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1163              :       TYPE(dbcsr_type), INTENT(IN)                       :: mat_phi_mu_l
    1164              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_Z_lP
    1165              :       TYPE(dbcsr_distribution_type), INTENT(OUT)         :: dist_Z
    1166              :       INTEGER, DIMENSION(:), POINTER                     :: row_size_grid, row_dist_grid, &
    1167              :                                                             col_dist_ri, ri_blk_sizes
    1168              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: row_offset
    1169              :       INTEGER, INTENT(OUT)                               :: myprow, mypcol, max_ao_size
    1170              : 
    1171              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'prepare_Z_lP_auto_ri'
    1172              : 
    1173              :       INTEGER                                            :: handle, i_blk, iatom, npcol
    1174              :       TYPE(dbcsr_distribution_type)                      :: dist_phi
    1175              : 
    1176            4 :       CALL timeset(routineN, handle)
    1177              : 
    1178            4 :       CALL dbcsr_get_info(mat_phi_mu_l, row_blk_size=row_size_grid, distribution=dist_phi)
    1179              :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist_grid, npcols=npcol, &
    1180            4 :                                   myprow=myprow, mypcol=mypcol)
    1181           12 :       ALLOCATE (row_offset(SIZE(row_size_grid)))
    1182            4 :       row_offset(1) = 0
    1183           12 :       DO i_blk = 2, SIZE(row_size_grid)
    1184           12 :          row_offset(i_blk) = row_offset(i_blk - 1) + row_size_grid(i_blk - 1)
    1185              :       END DO
    1186              : 
    1187           20 :       ALLOCATE (ri_blk_sizes(bs_env%n_atom), col_dist_ri(bs_env%n_atom))
    1188           32 :       ri_blk_sizes = bs_env%auto_ri%sizes_opt_RI
    1189           16 :       DO iatom = 1, bs_env%n_atom
    1190           16 :          col_dist_ri(iatom) = MOD(iatom - 1, npcol)
    1191              :       END DO
    1192              :       CALL dbcsr_distribution_new(dist_Z, template=dist_phi, row_dist=row_dist_grid, &
    1193            4 :                                   col_dist=col_dist_ri)
    1194              :       CALL dbcsr_create(mat_Z_lP, name='mat_Z_lP localized AA/AB', dist=dist_Z, &
    1195              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size_grid, &
    1196            4 :                         col_blk_size=ri_blk_sizes)
    1197            4 :       CALL dbcsr_reserve_all_blocks(mat_Z_lP)
    1198            4 :       CALL dbcsr_set(mat_Z_lP, 0.0_dp)
    1199              : 
    1200            4 :       max_ao_size = 0
    1201           16 :       DO iatom = 1, bs_env%n_atom
    1202              :          max_ao_size = MAX(max_ao_size, bs_env%i_ao_end_from_atom(iatom) - &
    1203           16 :                            bs_env%i_ao_start_from_atom(iatom) + 1)
    1204              :       END DO
    1205              : 
    1206            4 :       CALL timestop(handle)
    1207              : 
    1208            8 :    END SUBROUTINE prepare_Z_lP_auto_ri
    1209              : 
    1210              : ! **************************************************************************************************
    1211              : !> \brief Tests whether every fitted atomic block can use the complete RI-RS grid.
    1212              : !>
    1213              : !>        For a block centered on A, every grid point must satisfy
    1214              : !>
    1215              : !>          |r_l - R_A| <= R_A^fit,
    1216              : !>
    1217              : !>        and every atom B whose AOs can contribute must satisfy
    1218              : !>
    1219              : !>          |R_B - R_A| <= R_B^AO + R_A^fit.
    1220              : !>
    1221              : !>        Pair-midpoint grids do not satisfy this atom-centered criterion in general.
    1222              : !> \param bs_env ...
    1223              : !> \param ri_rs_grid_points ...
    1224              : !> \param common_grid_available ...
    1225              : !> \param have_fitted_columns ...
    1226              : ! **************************************************************************************************
    1227            4 :    SUBROUTINE common_Z_lP_grid_available(bs_env, ri_rs_grid_points, &
    1228              :                                          common_grid_available, have_fitted_columns)
    1229              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1230              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    1231              :       LOGICAL, INTENT(OUT)                               :: common_grid_available, &
    1232              :                                                             have_fitted_columns
    1233              : 
    1234              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'common_Z_lP_grid_available'
    1235              : 
    1236              :       INTEGER                                            :: AB_block, atom_A, atom_B, fit_atom, &
    1237              :                                                             handle, iatom, igrid, n_to_a, natom
    1238            4 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: active_atom
    1239              :       REAL(KIND=dp)                                      :: cutoff_ri
    1240              :       REAL(KIND=dp), DIMENSION(3)                        :: center
    1241            4 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1242              : 
    1243            4 :       CALL timeset(routineN, handle)
    1244              : 
    1245            4 :       particle_set => bs_env%ri_rs%particle_set
    1246            4 :       natom = bs_env%n_atom
    1247            4 :       common_grid_available = .TRUE.
    1248            4 :       have_fitted_columns = .FALSE.
    1249           12 :       ALLOCATE (active_atom(natom))
    1250           16 :       DO fit_atom = 1, natom
    1251           12 :          IF (bs_env%auto_ri%sizes_opt_RI(fit_atom) == 0) CYCLE
    1252           12 :          have_fitted_columns = .TRUE.
    1253           12 :          active_atom = .FALSE.
    1254           66 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1255           54 :             atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1256           54 :             atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1257           54 :             n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1258           54 :             IF (.NOT. (fit_atom == atom_A .AND. n_to_a > 0) .AND. &
    1259              :                 .NOT. (fit_atom == atom_B .AND. &
    1260              :                        n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block))) CYCLE
    1261           18 :             active_atom(atom_A) = .TRUE.
    1262           30 :             IF (atom_B /= atom_A) active_atom(atom_B) = .TRUE.
    1263              :          END DO
    1264              : 
    1265           48 :          center = particle_set(fit_atom)%r
    1266              :          cutoff_ri = 0.0_dp
    1267           48 :          DO iatom = 1, natom
    1268           36 :             IF (.NOT. active_atom(iatom)) CYCLE
    1269              :             cutoff_ri = MAX(cutoff_ri, bs_env%ri_metric%cutoff_radius + &
    1270              :                             bs_env%ri_rs%radius_ri_per_atom(iatom) + &
    1271          102 :                             NORM2(center - particle_set(iatom)%r))
    1272              :          END DO
    1273              : 
    1274          390 :          DO igrid = 1, bs_env%ri_rs%n_grid_points
    1275         1524 :             IF (NORM2(ri_rs_grid_points(1:3, igrid) - center) > cutoff_ri) THEN
    1276            0 :                common_grid_available = .FALSE.
    1277            0 :                EXIT
    1278              :             END IF
    1279              :          END DO
    1280           12 :          IF (.NOT. common_grid_available) EXIT
    1281              : 
    1282           48 :          DO iatom = 1, natom
    1283          144 :             IF (NORM2(particle_set(iatom)%r - center) > &
    1284           12 :                 bs_env%ri_rs%radius_ao_per_atom(iatom) + cutoff_ri) THEN
    1285            0 :                common_grid_available = .FALSE.
    1286            0 :                EXIT
    1287              :             END IF
    1288              :          END DO
    1289           16 :          IF (.NOT. common_grid_available) EXIT
    1290              :       END DO
    1291            4 :       DEALLOCATE (active_atom)
    1292              : 
    1293            4 :       CALL timestop(handle)
    1294              : 
    1295            4 :    END SUBROUTINE common_Z_lP_grid_available
    1296              : 
    1297              : ! **************************************************************************************************
    1298              : !> \brief Evaluates the AO collocation matrix on the complete RI-RS grid,
    1299              : !>
    1300              : !>          ϕ_lμ = ϕ_μ(r_l),  l = 1, ..., N_grid.
    1301              : !>
    1302              : !>        "Complete common grid" means that the same full set of grid points is valid for every
    1303              : !>        optimized RI column block. The enclosing sphere is only an implementation device passed
    1304              : !>        to build_phi_on_sphere; it contains every r_l and every AO center, so its chosen center
    1305              : !>        does not select an AA or AB contraction.
    1306              : !> \param bs_env ...
    1307              : !> \param qs_kind_set ...
    1308              : !> \param ri_rs_grid_points ...
    1309              : !> \param local_grid_idx ...
    1310              : !> \param ngrid ...
    1311              : !> \param phi_local ...
    1312              : !> \param ao_atoms global indices of the active AO atoms
    1313              : !> \param ao_col_start local phi column starts, including the terminal column offset
    1314              : !> \param ao_nonzero exact nonzero AO support on the retained grid rows
    1315              : ! **************************************************************************************************
    1316            8 :    SUBROUTINE build_phi_on_complete_grid(bs_env, qs_kind_set, &
    1317            4 :                                          ri_rs_grid_points, local_grid_idx, ngrid, &
    1318              :                                          phi_local, ao_atoms, ao_col_start, ao_nonzero)
    1319              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1320              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    1321              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    1322              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: local_grid_idx
    1323              :       INTEGER, INTENT(OUT)                               :: ngrid
    1324              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    1325              :          INTENT(OUT)                                     :: phi_local
    1326              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: ao_atoms, ao_col_start
    1327              :       LOGICAL, ALLOCATABLE, INTENT(OUT)                  :: ao_nonzero(:, :)
    1328              : 
    1329              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_phi_on_complete_grid'
    1330              : 
    1331              :       INTEGER                                            :: handle, iatom, igrid, reference_atom
    1332              :       REAL(KIND=dp)                                      :: cutoff_ri
    1333              :       REAL(KIND=dp), DIMENSION(3)                        :: center
    1334            4 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1335              : 
    1336            4 :       CALL timeset(routineN, handle)
    1337              : 
    1338            4 :       particle_set => bs_env%ri_rs%particle_set
    1339            4 :       reference_atom = 1
    1340           16 :       center = particle_set(reference_atom)%r
    1341            4 :       cutoff_ri = 0.0_dp
    1342          130 :       DO igrid = 1, bs_env%ri_rs%n_grid_points
    1343          508 :          cutoff_ri = MAX(cutoff_ri, NORM2(ri_rs_grid_points(1:3, igrid) - center))
    1344              :       END DO
    1345           16 :       DO iatom = 1, bs_env%n_atom
    1346           52 :          cutoff_ri = MAX(cutoff_ri, NORM2(particle_set(iatom)%r - center))
    1347              :       END DO
    1348            4 :       cutoff_ri = cutoff_ri + 1.0_dp
    1349              : 
    1350              :       CALL build_phi_on_sphere(bs_env, qs_kind_set, &
    1351              :                                ri_rs_grid_points, reference_atom, cutoff_ri, &
    1352              :                                local_grid_idx, ngrid, phi_local, ao_atoms, ao_col_start, &
    1353            4 :                                center=center, ao_nonzero=ao_nonzero)
    1354              : 
    1355            4 :       CALL timestop(handle)
    1356              : 
    1357            4 :    END SUBROUTINE build_phi_on_complete_grid
    1358              : 
    1359              : ! **************************************************************************************************
    1360              : !> \brief Writes Z_l,p0+p += z_lp into the process-owned grid-row blocks of one atomic column
    1361              : !>        block. The routine performs no explicit MPI communication; DBCSR summation combines
    1362              : !>        contributions when a block receives columns from more than one contraction group.
    1363              : !> \param mat_Z_lP ...
    1364              : !> \param z_block ...
    1365              : !> \param local_grid_idx ...
    1366              : !> \param n_local_grid ...
    1367              : !> \param atom_index ...
    1368              : !> \param first_column ...
    1369              : !> \param atom_block_size ...
    1370              : !> \param r_blk_sizes ...
    1371              : !> \param row_offset ...
    1372              : !> \param row_dist ...
    1373              : !> \param myprow ...
    1374              : !> \param eps_filter ...
    1375              : ! **************************************************************************************************
    1376           18 :    SUBROUTINE add_Z_lP_columns(mat_Z_lP, z_block, local_grid_idx, n_local_grid, atom_index, &
    1377           18 :                                first_column, atom_block_size, r_blk_sizes, row_offset, row_dist, &
    1378              :                                myprow, eps_filter)
    1379              : 
    1380              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_Z_lP
    1381              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: z_block
    1382              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: local_grid_idx
    1383              :       INTEGER, INTENT(IN)                                :: n_local_grid, atom_index, first_column, &
    1384              :                                                             atom_block_size
    1385              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: r_blk_sizes, row_offset, row_dist
    1386              :       INTEGER, INTENT(IN)                                :: myprow
    1387              :       REAL(KIND=dp), INTENT(IN)                          :: eps_filter
    1388              : 
    1389              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'add_Z_lP_columns'
    1390              : 
    1391              :       INTEGER                                            :: current_chunk_size, g_pt, handle, i_blk, &
    1392              :                                                             loc_ptr, ncolumn, r_end, r_start
    1393              :       LOGICAL                                            :: row_owned
    1394           18 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: Z_blk
    1395              : 
    1396           18 :       CALL timeset(routineN, handle)
    1397              : 
    1398           18 :       ncolumn = SIZE(z_block, 2)
    1399           18 :       CPASSERT(first_column > 0)
    1400           18 :       CPASSERT(first_column + ncolumn - 1 <= atom_block_size)
    1401          126 :       ALLOCATE (Z_blk(MAXVAL(r_blk_sizes), atom_block_size), source=0.0_dp)
    1402           18 :       loc_ptr = 1
    1403           72 :       DO i_blk = 1, SIZE(r_blk_sizes)
    1404           54 :          r_start = row_offset(i_blk) + 1
    1405           54 :          r_end = row_offset(i_blk) + r_blk_sizes(i_blk)
    1406           54 :          current_chunk_size = r_blk_sizes(i_blk)
    1407           54 :          row_owned = row_dist(i_blk) == myprow
    1408           54 :          Z_blk = 0.0_dp
    1409          684 :          DO WHILE (loc_ptr <= n_local_grid)
    1410          666 :             g_pt = local_grid_idx(loc_ptr)
    1411          666 :             IF (g_pt > r_end) EXIT
    1412          630 :             IF (row_owned) THEN
    1413              :                Z_blk(g_pt - r_start + 1, first_column:first_column + ncolumn - 1) = &
    1414         1302 :                   z_block(loc_ptr, 1:ncolumn)
    1415              :             END IF
    1416          666 :             loc_ptr = loc_ptr + 1
    1417              :          END DO
    1418         3750 :          IF (row_owned .AND. MAXVAL(ABS(Z_blk(1:current_chunk_size, :))) > eps_filter) THEN
    1419              :             CALL dbcsr_put_block(mat_Z_lP, row=i_blk, col=atom_index, &
    1420           27 :                                  block=Z_blk(1:current_chunk_size, :), summation=.TRUE.)
    1421              :          END IF
    1422              :       END DO
    1423           18 :       DEALLOCATE (Z_blk)
    1424              : 
    1425           18 :       CALL timestop(handle)
    1426              : 
    1427           18 :    END SUBROUTINE add_Z_lP_columns
    1428              : 
    1429              : ! **************************************************************************************************
    1430              : !> \brief Computes d_lp = Σ_{μνP} ϕ_μ(r_l)ϕ_ν(r_l)(μν|P)U_Pp for all AA and AB
    1431              : !>        columns p of one RI-RS matrix block. P can belong to either atom of an AB contraction.
    1432              : !> \param bs_env ...
    1433              : !> \param ctx ...
    1434              : !> \param phi_val ...
    1435              : !> \param ao_atoms global indices of the active AO atoms
    1436              : !> \param ao_col_start local phi column starts, including the terminal column offset
    1437              : !> \param ao_nonzero exact nonzero AO support on the retained grid rows
    1438              : !> \param d_lp ...
    1439              : !> \param ri_atoms global indices of contributing RI atoms in ascending order
    1440              : !> \param ri_coefficients ...
    1441              : !> \param max_ao_size ...
    1442              : !> \param atom_j_mepos ...
    1443              : !> \param atom_j_stride ...
    1444              : ! **************************************************************************************************
    1445           12 :    SUBROUTINE compute_d_lp_auto_ri(bs_env, ctx, phi_val, ao_atoms, ao_col_start, ao_nonzero, d_lp, &
    1446            6 :                                    ri_atoms, ri_coefficients, max_ao_size, atom_j_mepos, &
    1447              :                                    atom_j_stride)
    1448              : !$    USE OMP_LIB, ONLY: omp_get_max_threads, omp_get_thread_num
    1449              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1450              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1451              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    1452              :          INTENT(IN)                                      :: phi_val
    1453              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_atoms, ao_col_start
    1454              :       LOGICAL, DIMENSION(:, :), INTENT(IN)               :: ao_nonzero
    1455              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp
    1456              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :, :), INTENT(IN) :: ri_coefficients
    1457              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ri_atoms
    1458              :       INTEGER, INTENT(IN)                                :: max_ao_size, atom_j_mepos, atom_j_stride
    1459              : 
    1460              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_d_lp_auto_ri'
    1461              :       INTEGER, PARAMETER                                 :: grid_chunk = 1024
    1462              : 
    1463              :       INTEGER :: jj, kk, active_column, iatom, jatom, katom, c, handle, i_thread, j, jk_idx, jsize, &
    1464              :                  jstart, k, ksize, kstart, l, l0, &
    1465              :                  max_active, nactive, ncol, n_grid, nRI_ref, nthreads, ri, slot, thread_id
    1466            6 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: atom_ncol
    1467            6 :       INTEGER, ALLOCATABLE, DIMENSION(:, :)              :: atom_column
    1468              :       LOGICAL                                            :: any_integral, screened, single_atom
    1469              :       REAL(KIND=dp)                                      :: pair_factor
    1470            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: int_2d_prv, rho_chunk
    1471            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: coefficient_compact, d_lp_threads, &
    1472            6 :                                                             int_3c_prv, int_3c_atom
    1473            6 :       TYPE(gw_3c_ws_type)                                :: ws
    1474              : 
    1475            6 :       INTEGER, ALLOCATABLE, DIMENSION(:) :: grid_index
    1476              :       INTEGER :: n_grid_pair, grid_l, point
    1477            6 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: grid_result
    1478              : 
    1479            6 :       CALL timeset(routineN, handle)
    1480            6 :       n_grid = SIZE(d_lp, 1)
    1481            6 :       ncol = SIZE(d_lp, 2)
    1482            6 :       single_atom = SIZE(ri_atoms) == 1
    1483            6 :       nthreads = 1
    1484            6 : !$    nthreads = omp_get_max_threads()
    1485            6 :       CPASSERT(SIZE(ri_coefficients, 2) == ncol)
    1486            6 :       CPASSERT(SIZE(ri_coefficients, 3) == SIZE(ri_atoms))
    1487            6 :       CPASSERT(SIZE(ri_atoms) > 0)
    1488            6 :       max_active = 0
    1489            6 :       IF (.NOT. single_atom) THEN
    1490            0 :          ALLOCATE (atom_ncol(SIZE(ri_atoms)), atom_column(ncol, SIZE(ri_atoms)))
    1491            0 :          atom_ncol = 0
    1492            0 :          atom_column = 0
    1493            0 :          DO slot = 1, SIZE(ri_atoms)
    1494            0 :             nRI_ref = get_ref_RI_size(bs_env, ri_atoms(slot))
    1495            0 :             DO ri = 1, ncol
    1496            0 :                IF (.NOT. ANY(ri_coefficients(1:nRI_ref, ri, slot) /= 0.0_dp)) CYCLE
    1497            0 :                atom_ncol(slot) = atom_ncol(slot) + 1
    1498            0 :                atom_column(atom_ncol(slot), slot) = ri
    1499              :             END DO
    1500              :          END DO
    1501            0 :          max_active = MAXVAL(atom_ncol)
    1502            0 :          CPASSERT(max_active > 0)
    1503            0 :          ALLOCATE (coefficient_compact(SIZE(ri_coefficients, 1), max_active, &
    1504            0 :                                        SIZE(ri_atoms)), source=0.0_dp)
    1505            0 :          DO slot = 1, SIZE(ri_atoms)
    1506            0 :             nRI_ref = get_ref_RI_size(bs_env, ri_atoms(slot))
    1507            0 :             DO active_column = 1, atom_ncol(slot)
    1508            0 :                ri = atom_column(active_column, slot)
    1509              :                coefficient_compact(1:nRI_ref, active_column, slot) = &
    1510            0 :                   ri_coefficients(1:nRI_ref, ri, slot)
    1511              :             END DO
    1512              :          END DO
    1513              :       END IF
    1514           30 :       ALLOCATE (d_lp_threads(n_grid, ncol, nthreads), source=0.0_dp)
    1515              : 
    1516              :       !$OMP PARALLEL DEFAULT(NONE) &
    1517              :       !$OMP SHARED(bs_env, ctx, phi_val, ao_atoms, ao_col_start, ao_nonzero, d_lp, n_grid, ri_coefficients, &
    1518              :       !$OMP        ri_atoms, single_atom, max_ao_size, atom_j_mepos, atom_j_stride, ncol, &
    1519              :       !$OMP        d_lp_threads, nthreads, atom_ncol, &
    1520              :       !$OMP        atom_column, coefficient_compact, max_active) &
    1521              :       !$OMP PRIVATE(grid_index, n_grid_pair, grid_l, point, grid_result, &
    1522              :       !$OMP         jj, kk, active_column, iatom, jatom, katom, c, i_thread, j, jk_idx, jsize, jstart, &
    1523              :       !$OMP         k, ksize, kstart, l, l0, &
    1524              :       !$OMP         nactive, nRI_ref, ri, slot, any_integral, screened, pair_factor, &
    1525              :       !$OMP         int_2d_prv, rho_chunk, &
    1526            6 :       !$OMP         int_3c_prv, int_3c_atom, ws, thread_id)
    1527              : 
    1528              :       thread_id = 1
    1529              : !$    thread_id = omp_get_thread_num() + 1
    1530              : 
    1531              :       CALL gw_3c_ws_create(ws, ctx)
    1532              :       ALLOCATE (int_3c_prv(max_ao_size, max_ao_size, ncol))
    1533              :       IF (.NOT. single_atom) ALLOCATE (int_3c_atom(max_ao_size, max_ao_size, max_active))
    1534              :       ALLOCATE (int_2d_prv(max_ao_size*max_ao_size, ncol))
    1535              :       ALLOCATE (rho_chunk(grid_chunk, max_ao_size*max_ao_size))
    1536              :       ALLOCATE (grid_index(n_grid), grid_result(grid_chunk, ncol))
    1537              : 
    1538              :       !$OMP DO SCHEDULE(DYNAMIC)
    1539              :       DO jj = 1, SIZE(ao_atoms)
    1540              :          jatom = ao_atoms(jj)
    1541              :          IF (MOD(jatom - 1, atom_j_stride) /= atom_j_mepos) CYCLE
    1542              :          DO kk = jj, SIZE(ao_atoms)
    1543              :             katom = ao_atoms(kk)
    1544              :             jstart = ao_col_start(jj)
    1545              :             kstart = ao_col_start(kk)
    1546              :             jsize = ao_col_start(jj + 1) - jstart
    1547              :             ksize = ao_col_start(kk + 1) - kstart
    1548              : 
    1549              :             n_grid_pair = 0
    1550              :             DO grid_l = 1, n_grid
    1551              :                IF (.NOT. (ao_nonzero(grid_l, jj) .AND. ao_nonzero(grid_l, kk))) CYCLE
    1552              :                n_grid_pair = n_grid_pair + 1
    1553              :                grid_index(n_grid_pair) = grid_l
    1554              :             END DO
    1555              :             IF (n_grid_pair == 0) CYCLE
    1556              :             int_3c_prv(1:jsize, 1:ksize, 1:ncol) = 0.0_dp
    1557              :             IF (single_atom) THEN
    1558              :                ! A single contributor writes the transformed block without packing or scattering.
    1559              :                CALL build_3c_integral_block_auto_ri_ctx( &
    1560              :                   int_3c_prv(1:jsize, 1:ksize, 1:ncol), ctx, ws, &
    1561              :                   atom_j=jatom, atom_k=katom, atom_i=ri_atoms(1), &
    1562              :                   transform=ri_coefficients(:, :, 1), transform_row=1, screened=screened)
    1563              :                IF (screened) CYCLE
    1564              :             ELSE
    1565              :                any_integral = .FALSE.
    1566              :                DO slot = 1, SIZE(ri_atoms)
    1567              :                   iatom = ri_atoms(slot)
    1568              :                   nactive = atom_ncol(slot)
    1569              :                   IF (nactive == 0) CYCLE
    1570              :                   nRI_ref = get_ref_RI_size(bs_env, iatom)
    1571              :                   int_3c_atom(1:jsize, 1:ksize, 1:nactive) = 0.0_dp
    1572              :                   CALL build_3c_integral_block_auto_ri_ctx( &
    1573              :                      int_3c_atom(1:jsize, 1:ksize, 1:nactive), ctx, ws, &
    1574              :                      atom_j=jatom, atom_k=katom, atom_i=iatom, &
    1575              :                      transform=coefficient_compact(1:nRI_ref, 1:nactive, slot), &
    1576              :                      transform_row=1, screened=screened)
    1577              :                   IF (.NOT. screened) THEN
    1578              :                      any_integral = .TRUE.
    1579              :                      DO active_column = 1, nactive
    1580              :                         ri = atom_column(active_column, slot)
    1581              :                         int_3c_prv(1:jsize, 1:ksize, ri) = &
    1582              :                            int_3c_prv(1:jsize, 1:ksize, ri) + &
    1583              :                            int_3c_atom(1:jsize, 1:ksize, active_column)
    1584              :                      END DO
    1585              :                   END IF
    1586              :                END DO
    1587              :                IF (.NOT. any_integral) CYCLE
    1588              :             END IF
    1589              : 
    1590              :             DO ri = 1, ncol
    1591              :                DO k = 1, ksize
    1592              :                   DO j = 1, jsize
    1593              :                      jk_idx = (k - 1)*jsize + j
    1594              :                      int_2d_prv(jk_idx, ri) = int_3c_prv(j, k, ri)
    1595              :                   END DO
    1596              :                END DO
    1597              :             END DO
    1598              : 
    1599              :             pair_factor = 1.0_dp
    1600              :             IF (jatom /= katom) pair_factor = 2.0_dp
    1601              :             DO l0 = 1, n_grid_pair, grid_chunk
    1602              :                c = MIN(grid_chunk, n_grid_pair - l0 + 1)
    1603              :                DO k = 1, ksize
    1604              :                   DO j = 1, jsize
    1605              :                      jk_idx = (k - 1)*jsize + j
    1606              :                      DO l = 1, c
    1607              :                         point = grid_index(l0 + l - 1)
    1608              :                         rho_chunk(l, jk_idx) = phi_val(point, jstart + j - 1)* &
    1609              :                                                phi_val(point, kstart + k - 1)
    1610              :                      END DO
    1611              :                   END DO
    1612              :                END DO
    1613              :                CALL dgemm('N', 'N', c, ncol, jsize*ksize, pair_factor, rho_chunk, grid_chunk, &
    1614              :                           int_2d_prv, max_ao_size*max_ao_size, 0.0_dp, &
    1615              :                           grid_result, grid_chunk)
    1616              :                DO ri = 1, ncol
    1617              :                   DO l = 1, c
    1618              :                      point = grid_index(l0 + l - 1)
    1619              :                      d_lp_threads(point, ri, thread_id) = &
    1620              :                         d_lp_threads(point, ri, thread_id) + grid_result(l, ri)
    1621              :                   END DO
    1622              :                END DO
    1623              :             END DO
    1624              :          END DO
    1625              :       END DO
    1626              :       !$OMP END DO
    1627              : 
    1628              :       !$OMP DO COLLAPSE(2) SCHEDULE(STATIC)
    1629              :       DO ri = 1, ncol
    1630              :          DO l = 1, n_grid
    1631              :             DO i_thread = 1, nthreads
    1632              :                d_lp(l, ri) = d_lp(l, ri) + d_lp_threads(l, ri, i_thread)
    1633              :             END DO
    1634              :          END DO
    1635              :       END DO
    1636              :       !$OMP END DO
    1637              :       IF (.NOT. single_atom) DEALLOCATE (int_3c_atom)
    1638              :       DEALLOCATE (int_3c_prv, int_2d_prv, rho_chunk)
    1639              :       DEALLOCATE (grid_index, grid_result)
    1640              :       CALL gw_3c_ws_release(ws)
    1641              :       !$OMP END PARALLEL
    1642              : 
    1643            6 :       IF (.NOT. single_atom) DEALLOCATE (coefficient_compact, atom_column, atom_ncol)
    1644            6 :       DEALLOCATE (d_lp_threads)
    1645              : 
    1646            6 :       CALL timestop(handle)
    1647              : 
    1648           12 :    END SUBROUTINE compute_d_lp_auto_ri
    1649              : 
    1650              : ! **************************************************************************************************
    1651              : !> \brief Computes d_lp = Σ_A Σ_{P∈A} d_lP U_Pp in rank-sized batches on a common grid.
    1652              : !> \param bs_env ...
    1653              : !> \param ctx ...
    1654              : !> \param phi_val ...
    1655              : !> \param ao_atoms global indices of the active AO atoms
    1656              : !> \param ao_col_start local phi column starts, including the terminal column offset
    1657              : !> \param ao_nonzero exact nonzero AO support on the retained grid rows
    1658              : !> \param d_lp ...
    1659              : !> \param n_grid ...
    1660              : !> \param max_ao_size ...
    1661              : !> \param mepos ...
    1662              : !> \param num_pe ...
    1663              : ! **************************************************************************************************
    1664            4 :    SUBROUTINE compute_d_lp_auto_ri_batch(bs_env, ctx, phi_val, ao_atoms, ao_col_start, ao_nonzero, d_lp, n_grid, &
    1665              :                                          max_ao_size, mepos, num_pe)
    1666              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1667              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1668              :       REAL(KIND=dp), CONTIGUOUS, DIMENSION(:, :), &
    1669              :          INTENT(IN)                                      :: phi_val
    1670              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ao_atoms, ao_col_start
    1671              :       LOGICAL, DIMENSION(:, :), INTENT(IN)               :: ao_nonzero
    1672              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT)      :: d_lp
    1673              :       INTEGER, INTENT(IN)                                :: n_grid, max_ao_size, mepos, num_pe
    1674              : 
    1675              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_d_lp_auto_ri_batch'
    1676              : 
    1677              :       INTEGER                                            :: column, handle, iatom, n_done, n_total, &
    1678              :                                                             ncol_batch
    1679            4 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: global_map
    1680              :       REAL(KIND=dp)                                      :: item_start_time
    1681            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: d_lp_batch
    1682            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: U_Pp
    1683              : 
    1684            4 :       CALL timeset(routineN, handle)
    1685              : 
    1686            4 :       n_total = 0
    1687            4 :       DO iatom = mepos + 1, bs_env%n_atom, num_pe
    1688            6 :          n_total = n_total + 1
    1689              :       END DO
    1690            4 :       n_done = 0
    1691              : 
    1692           10 :       DO iatom = mepos + 1, bs_env%n_atom, num_pe
    1693            6 :          item_start_time = m_walltime()
    1694            6 :          CALL collect_auto_ri_columns_for_atom(bs_env, iatom, U_Pp, global_map)
    1695            6 :          ncol_batch = SIZE(global_map)
    1696            6 :          IF (ncol_batch == 0) THEN
    1697            0 :             DEALLOCATE (U_Pp, global_map)
    1698            0 :             n_done = n_done + 1
    1699              :             CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    1700            0 :                                      m_walltime() - item_start_time)
    1701            0 :             CYCLE
    1702              :          END IF
    1703              : 
    1704           24 :          ALLOCATE (d_lp_batch(n_grid, ncol_batch), source=0.0_dp)
    1705              :          CALL compute_d_lp_auto_ri(bs_env, ctx, phi_val, ao_atoms, ao_col_start, ao_nonzero, d_lp_batch, &
    1706           12 :                                    [iatom], U_Pp, max_ao_size, 0, 1)
    1707           44 :          DO column = 1, ncol_batch
    1708         1409 :             d_lp(:, global_map(column)) = d_lp(:, global_map(column)) + d_lp_batch(:, column)
    1709              :          END DO
    1710            6 :          DEALLOCATE (U_Pp, global_map, d_lp_batch)
    1711            6 :          n_done = n_done + 1
    1712              :          CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    1713           10 :                                   m_walltime() - item_start_time)
    1714              :       END DO
    1715            4 :       CALL timestop(handle)
    1716              : 
    1717            4 :    END SUBROUTINE compute_d_lp_auto_ri_batch
    1718              : 
    1719              : ! **************************************************************************************************
    1720              : !> \brief Collects every optimized column p containing reference functions P on atom A. It returns
    1721              : !>
    1722              : !>          U_Pp^A
    1723              : !>
    1724              : !>        together with the global optimized-column index of q.
    1725              : !> \param bs_env ...
    1726              : !> \param iatom ...
    1727              : !> \param U_Pp ...
    1728              : !> \param global_map ...
    1729              : ! **************************************************************************************************
    1730            6 :    SUBROUTINE collect_auto_ri_columns_for_atom(bs_env, iatom, U_Pp, global_map)
    1731              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1732              :       INTEGER, INTENT(IN)                                :: iatom
    1733              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: U_Pp(:, :, :)
    1734              :       INTEGER, ALLOCATABLE, INTENT(OUT)                  :: global_map(:)
    1735              : 
    1736              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'collect_auto_ri_columns_for_atom'
    1737              : 
    1738              :       INTEGER :: AB_block, atom_A, atom_B, column, global_column, handle, local_column, ncol, &
    1739              :          ncol_batch, nRI_ref, nRI_ref_A, output_offset, row_first
    1740            6 :       REAL(KIND=dp), ALLOCATABLE                         :: U_Pp_AB(:, :)
    1741              : 
    1742            6 :       CALL timeset(routineN, handle)
    1743              : 
    1744            6 :       ncol_batch = 0
    1745           33 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1746           27 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1747           27 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1748           33 :          IF (iatom == atom_A .OR. iatom == atom_B) THEN
    1749           12 :             ncol_batch = ncol_batch + bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1750              :          END IF
    1751              :       END DO
    1752              : 
    1753            6 :       nRI_ref = get_ref_RI_size(bs_env, iatom)
    1754           24 :       ALLOCATE (U_Pp(nRI_ref, ncol_batch, 1), source=0.0_dp)
    1755           18 :       ALLOCATE (global_map(ncol_batch))
    1756            6 :       column = 0
    1757           33 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1758           27 :          atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1759           27 :          atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1760           27 :          IF (iatom /= atom_A .AND. iatom /= atom_B) CYCLE
    1761           12 :          ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1762           12 :          CALL get_U_Pp_AB(bs_env%auto_ri, AB_block, U_Pp_AB)
    1763           12 :          row_first = 1
    1764           12 :          IF (iatom == atom_B .AND. atom_B /= atom_A) THEN
    1765            3 :             nRI_ref_A = get_ref_RI_size(bs_env, atom_A)
    1766            3 :             row_first = 1 + nRI_ref_A
    1767              :          END IF
    1768              :          U_Pp(:, column + 1:column + ncol, 1) = &
    1769              :             U_Pp_AB( &
    1770         3892 :             row_first:row_first + nRI_ref - 1, 1:ncol)
    1771              : 
    1772           50 :          DO local_column = 1, ncol
    1773           38 :             IF (local_column <= bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)) THEN
    1774              :                output_offset = SUM(bs_env%auto_ri%sizes_opt_RI(:atom_A - 1)) + &
    1775           30 :                                bs_env%auto_ri%AB_first_p_A(AB_block) - 1
    1776           22 :                global_column = output_offset + local_column
    1777              :             ELSE
    1778           16 :                CPASSERT(atom_B /= atom_A)
    1779              :                output_offset = SUM(bs_env%auto_ri%sizes_opt_RI(:atom_B - 1)) + &
    1780           40 :                                bs_env%auto_ri%AB_first_p_B(AB_block) - 1
    1781              :                global_column = output_offset + local_column - &
    1782           16 :                                bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1783              :             END IF
    1784           50 :             global_map(column + local_column) = global_column
    1785              :          END DO
    1786           12 :          column = column + ncol
    1787           33 :          DEALLOCATE (U_Pp_AB)
    1788              :       END DO
    1789            6 :       CPASSERT(column == ncol_batch)
    1790              : 
    1791            6 :       CALL timestop(handle)
    1792              : 
    1793           12 :    END SUBROUTINE collect_auto_ri_columns_for_atom
    1794              : 
    1795              : ! **************************************************************************************************
    1796              : !> \brief Unpacks one AB contraction matrix U_Pp; B=A denotes an AA block.
    1797              : !> \param auto_ri ...
    1798              : !> \param AB_block ...
    1799              : !> \param U_Pp ...
    1800              : ! **************************************************************************************************
    1801           12 :    SUBROUTINE get_U_Pp_AB(auto_ri, AB_block, U_Pp)
    1802              :       TYPE(auto_ri_type), INTENT(IN)                     :: auto_ri
    1803              :       INTEGER, INTENT(IN)                                :: AB_block
    1804              :       REAL(KIND=dp), ALLOCATABLE, INTENT(OUT)            :: U_Pp(:, :)
    1805              : 
    1806              :       INTEGER                                            :: first, last, ncolumn, nrow
    1807              : 
    1808           12 :       nrow = auto_ri%AB_size_ref_RI(AB_block)
    1809           12 :       ncolumn = auto_ri%AB_size_opt_RI(AB_block)
    1810           12 :       first = auto_ri%U_Pp_AB_offset(AB_block)
    1811           12 :       last = first + nrow*ncolumn - 1
    1812           48 :       ALLOCATE (U_Pp(nrow, ncolumn))
    1813           36 :       U_Pp(:, :) = RESHAPE(auto_ri%U_Pp_AB(first:last), [nrow, ncolumn])
    1814              : 
    1815           12 :    END SUBROUTINE get_U_Pp_AB
    1816              : 
    1817              : ! **************************************************************************************************
    1818              : !> \brief Computes the fitting radius for every optimized atomic column block. If q assigned to A
    1819              : !>        contains reference functions on B, the required radius is
    1820              : !>
    1821              : !>          R_A^fit = max_B [r_c + R_B^RI + |R_A - R_B|].
    1822              : !> \param bs_env ...
    1823              : !> \param radius ...
    1824              : ! **************************************************************************************************
    1825            0 :    SUBROUTINE compute_auto_ri_grid_radii(bs_env, radius)
    1826              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1827              :       REAL(KIND=dp), INTENT(OUT)                         :: radius(:)
    1828              : 
    1829              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_auto_ri_grid_radii'
    1830              : 
    1831              :       INTEGER                                            :: a, AB_block, b, handle, n_to_a, ncol
    1832              :       REAL(KIND=dp)                                      :: cutoff, distance
    1833            0 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1834              : 
    1835            0 :       CALL timeset(routineN, handle)
    1836              : 
    1837            0 :       particle_set => bs_env%ri_rs%particle_set
    1838            0 :       radius = 0.0_dp
    1839            0 :       cutoff = bs_env%ri_metric%cutoff_radius
    1840            0 :       DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1841            0 :          a = bs_env%auto_ri%AB_atom_A(AB_block)
    1842            0 :          b = bs_env%auto_ri%AB_atom_B(AB_block)
    1843            0 :          n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1844            0 :          ncol = bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1845            0 :          IF (n_to_a > 0) THEN
    1846            0 :             radius(a) = MAX(radius(a), cutoff + bs_env%ri_rs%radius_ri_per_atom(a))
    1847            0 :             IF (b /= a) THEN
    1848            0 :                distance = NORM2(particle_set(a)%r - particle_set(b)%r)
    1849            0 :                radius(a) = MAX(radius(a), cutoff + bs_env%ri_rs%radius_ri_per_atom(b) + distance)
    1850              :             END IF
    1851              :          END IF
    1852            0 :          IF (n_to_a < ncol) THEN
    1853            0 :             CPASSERT(b /= a)
    1854            0 :             distance = NORM2(particle_set(b)%r - particle_set(a)%r)
    1855              :             radius(b) = MAX(radius(b), cutoff + bs_env%ri_rs%radius_ri_per_atom(b), &
    1856            0 :                             cutoff + bs_env%ri_rs%radius_ri_per_atom(a) + distance)
    1857              :          END IF
    1858              :       END DO
    1859              : 
    1860            0 :       CALL timestop(handle)
    1861            0 :    END SUBROUTINE compute_auto_ri_grid_radii
    1862              : 
    1863              : ! **************************************************************************************************
    1864              : !> \brief Computes
    1865              : !>
    1866              : !>          d_lp = Σ_A Σ_{P∈A} d_lP U_Pp^A,
    1867              : !>          d_lP = Σ_μν ϕ_μ(r_l) ϕ_ν(r_l) (μν|P),
    1868              : !>
    1869              : !>        on the union of the atomic RI-RS grids needed by q. Each atom's three-center integrals are
    1870              : !>        evaluated once and transformed with U_Pp^A.
    1871              : !> \param qs_env ...
    1872              : !> \param bs_env ...
    1873              : !> \param ctx ...
    1874              : !> \param grid ...
    1875              : !> \param mat_phi ...
    1876              : !> \param mat_rhs ...
    1877              : !> \param max_ao_size ...
    1878              : ! **************************************************************************************************
    1879            0 :    SUBROUTINE compute_auto_ri_d_lp(qs_env, bs_env, ctx, grid, mat_phi, mat_rhs, max_ao_size)
    1880              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    1881              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    1882              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    1883              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: grid
    1884              :       TYPE(dbcsr_type), INTENT(IN)                       :: mat_phi
    1885              :       TYPE(dbcsr_type), INTENT(OUT)                      :: mat_rhs
    1886              :       INTEGER, INTENT(IN)                                :: max_ao_size
    1887              : 
    1888              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_auto_ri_d_lp'
    1889              : 
    1890              :       INTEGER :: AB_block, atom_A, atom_B, first, fit_atom, handle, handle_project, handle_rhs, l, &
    1891              :          last, n_done, n_first_p_ABs, n_to_a, n_total, n_union, natom, ncol, ngrid, npcol, nprow, &
    1892              :          ri_atom
    1893            0 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: ao_atoms, ao_col_start, global_map, &
    1894            0 :                                                             local_index, row_offset, union_index
    1895            0 :       INTEGER, DIMENSION(:), POINTER                     :: AB_row_dist, col_dist, &
    1896            0 :                                                             first_p_ABs_per_atom, retained_size, &
    1897            0 :                                                             row_dist, row_size
    1898            0 :       LOGICAL, ALLOCATABLE                               :: ao_nonzero(:, :)
    1899            0 :       LOGICAL, ALLOCATABLE, DIMENSION(:)                 :: needed_fit_atom, union_mask
    1900              :       REAL(KIND=dp)                                      :: item_start_time, radius
    1901            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: fit_radius
    1902            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: column_map, phi, rhs, union_grid
    1903            0 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: U_Pp
    1904              :       TYPE(cell_type), POINTER                           :: cell
    1905              :       TYPE(dbcsr_distribution_type)                      :: dist_AB, dist_phi, dist_t
    1906              :       TYPE(dbcsr_type)                                   :: AB_d_lp, transform
    1907              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    1908            0 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    1909            0 :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    1910              : 
    1911            0 :       CALL timeset(routineN, handle)
    1912              :       CALL get_qs_env(qs_env, para_env=para_env, particle_set=particle_set, &
    1913            0 :                       qs_kind_set=qs_kind_set, cell=cell)
    1914            0 :       natom = bs_env%n_atom
    1915            0 :       CALL dbcsr_get_info(mat_phi, row_blk_size=row_size, distribution=dist_phi)
    1916            0 :       CALL dbcsr_distribution_get(dist_phi, row_dist=row_dist, nprows=nprow, npcols=npcol)
    1917              :       ALLOCATE (first_p_ABs_per_atom(natom), col_dist(natom), AB_row_dist(natom), &
    1918            0 :                 retained_size(natom), row_offset(SIZE(row_size)))
    1919            0 :       retained_size(:) = bs_env%auto_ri%sizes_opt_RI
    1920            0 :       DO ri_atom = 1, natom
    1921            0 :          first_p_ABs_per_atom(ri_atom) = 0
    1922            0 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1923            0 :             IF (ri_atom == bs_env%auto_ri%AB_atom_A(AB_block) .OR. &
    1924            0 :                 ri_atom == bs_env%auto_ri%AB_atom_B(AB_block)) THEN
    1925              :                first_p_ABs_per_atom(ri_atom) = &
    1926              :                   first_p_ABs_per_atom(ri_atom) + &
    1927            0 :                   bs_env%auto_ri%AB_size_opt_RI(AB_block)
    1928              :             END IF
    1929              :          END DO
    1930            0 :          col_dist(ri_atom) = MOD(ri_atom - 1, npcol)
    1931            0 :          AB_row_dist(ri_atom) = MOD(ri_atom - 1, nprow)
    1932              :       END DO
    1933            0 :       row_offset(1) = 0
    1934            0 :       DO l = 2, SIZE(row_size)
    1935            0 :          row_offset(l) = row_offset(l - 1) + row_size(l - 1)
    1936              :       END DO
    1937              :       CALL dbcsr_distribution_new(dist_AB, template=dist_phi, &
    1938            0 :                                   row_dist=row_dist, col_dist=col_dist)
    1939              :       CALL dbcsr_create(AB_d_lp, name='AUTO_RI AA/AB d_lp', dist=dist_AB, &
    1940              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size, &
    1941            0 :                         col_blk_size=first_p_ABs_per_atom)
    1942              :       CALL dbcsr_distribution_new(dist_t, template=dist_phi, &
    1943            0 :                                   row_dist=AB_row_dist, col_dist=col_dist)
    1944              :       CALL dbcsr_create(transform, name='AUTO_RI U_Pp', dist=dist_t, &
    1945              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=first_p_ABs_per_atom, &
    1946            0 :                         col_blk_size=retained_size)
    1947              :       CALL dbcsr_create(mat_rhs, name='AUTO_RI optimized d_lp', dist=dist_AB, &
    1948              :                         matrix_type=dbcsr_type_no_symmetry, row_blk_size=row_size, &
    1949            0 :                         col_blk_size=retained_size)
    1950            0 :       ALLOCATE (fit_radius(natom))
    1951            0 :       CALL compute_auto_ri_grid_radii(bs_env, fit_radius)
    1952            0 :       ALLOCATE (needed_fit_atom(natom), union_mask(SIZE(grid, 2)))
    1953            0 :       n_total = 0
    1954            0 :       DO ri_atom = para_env%mepos + 1, natom, para_env%num_pe
    1955            0 :          n_total = n_total + 1
    1956              :       END DO
    1957            0 :       n_done = 0
    1958            0 :       CALL timeset(routineN//'_AB_d_lp', handle_rhs)
    1959            0 :       DO ri_atom = para_env%mepos + 1, natom, para_env%num_pe
    1960            0 :          item_start_time = m_walltime()
    1961            0 :          needed_fit_atom = .FALSE.
    1962            0 :          DO AB_block = 1, bs_env%auto_ri%AB_block_count
    1963            0 :             atom_A = bs_env%auto_ri%AB_atom_A(AB_block)
    1964            0 :             atom_B = bs_env%auto_ri%AB_atom_B(AB_block)
    1965            0 :             IF (ri_atom /= atom_A .AND. ri_atom /= atom_B) CYCLE
    1966            0 :             n_to_a = bs_env%auto_ri%AB_size_opt_RI_to_A(AB_block)
    1967            0 :             IF (n_to_a > 0) needed_fit_atom(atom_A) = .TRUE.
    1968            0 :             IF (n_to_a < bs_env%auto_ri%AB_size_opt_RI(AB_block)) THEN
    1969            0 :                needed_fit_atom(atom_B) = .TRUE.
    1970              :             END IF
    1971              :          END DO
    1972            0 :          IF (.NOT. ANY(needed_fit_atom)) THEN
    1973            0 :             n_done = n_done + 1
    1974              :             CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    1975            0 :                                      m_walltime() - item_start_time)
    1976            0 :             CYCLE
    1977              :          END IF
    1978            0 :          union_mask = .FALSE.
    1979            0 :          radius = 0.0_dp
    1980            0 :          DO fit_atom = 1, natom
    1981            0 :             IF (.NOT. needed_fit_atom(fit_atom)) CYCLE
    1982              :             radius = MAX(radius, fit_radius(fit_atom) + &
    1983            0 :                          NORM2(particle_set(fit_atom)%r - particle_set(ri_atom)%r))
    1984            0 :             DO l = 1, SIZE(grid, 2)
    1985            0 :                IF (NORM2(grid(:, l) - particle_set(fit_atom)%r) <= &
    1986            0 :                    fit_radius(fit_atom)) union_mask(l) = .TRUE.
    1987              :             END DO
    1988              :          END DO
    1989            0 :          n_union = COUNT(union_mask)
    1990            0 :          ALLOCATE (union_index(n_union), union_grid(3, n_union))
    1991            0 :          n_union = 0
    1992            0 :          DO l = 1, SIZE(grid, 2)
    1993            0 :             IF (.NOT. union_mask(l)) CYCLE
    1994            0 :             n_union = n_union + 1
    1995            0 :             union_index(n_union) = l
    1996            0 :             union_grid(:, n_union) = grid(:, l)
    1997              :          END DO
    1998              :          CALL build_phi_on_sphere(bs_env, qs_kind_set, union_grid, ri_atom, &
    1999              :                                   radius + 1.0_dp, local_index, ngrid, phi, ao_atoms, ao_col_start, &
    2000            0 :                                   ao_nonzero=ao_nonzero)
    2001            0 :          local_index(1:ngrid) = union_index(local_index(1:ngrid))
    2002            0 :          n_first_p_ABs = first_p_ABs_per_atom(ri_atom)
    2003            0 :          CALL collect_auto_ri_columns_for_atom(bs_env, ri_atom, U_Pp, global_map)
    2004            0 :          CPASSERT(SIZE(global_map) == n_first_p_ABs)
    2005            0 :          first = 1
    2006            0 :          DO fit_atom = 1, natom
    2007            0 :             ncol = retained_size(fit_atom)
    2008            0 :             last = first + ncol - 1
    2009            0 :             IF (ANY(global_map >= first .AND. global_map <= last)) THEN
    2010            0 :                ALLOCATE (column_map(n_first_p_ABs, ncol), source=0.0_dp)
    2011            0 :                DO l = 1, n_first_p_ABs
    2012            0 :                   IF (global_map(l) < first .OR. global_map(l) > last) CYCLE
    2013            0 :                   column_map(l, global_map(l) - first + 1) = 1.0_dp
    2014              :                END DO
    2015            0 :                CALL dbcsr_put_block(transform, ri_atom, fit_atom, column_map)
    2016            0 :                DEALLOCATE (column_map)
    2017              :             END IF
    2018            0 :             first = last + 1
    2019              :          END DO
    2020            0 :          ALLOCATE (rhs(ngrid, n_first_p_ABs), source=0.0_dp)
    2021              :          CALL compute_d_lp_auto_ri(bs_env, ctx, phi, ao_atoms, ao_col_start, ao_nonzero, rhs, &
    2022            0 :                                    [ri_atom], U_Pp, max_ao_size, 0, 1)
    2023            0 :          DEALLOCATE (ao_atoms, ao_col_start, ao_nonzero)
    2024              :          CALL store_Z_lP_columns(AB_d_lp, rhs, local_index, ngrid, n_first_p_ABs, ri_atom, &
    2025            0 :                                  row_size, row_offset, 0.0_dp)
    2026            0 :          DEALLOCATE (rhs, U_Pp, global_map, phi, local_index, &
    2027            0 :                      union_grid, union_index)
    2028            0 :          n_done = n_done + 1
    2029              :          CALL print_Z_lP_progress(bs_env, n_done, n_total, &
    2030            0 :                                   m_walltime() - item_start_time)
    2031              :       END DO
    2032            0 :       CALL dbcsr_finalize(transform)
    2033            0 :       CALL dbcsr_finalize(AB_d_lp)
    2034            0 :       CALL timestop(handle_rhs)
    2035            0 :       CALL timeset(routineN//'_transform', handle_project)
    2036              :       CALL dbcsr_multiply('N', 'N', 1.0_dp, AB_d_lp, transform, 0.0_dp, &
    2037            0 :                           mat_rhs, filter_eps=0.0_dp)
    2038            0 :       CALL timestop(handle_project)
    2039            0 :       CALL dbcsr_release(AB_d_lp)
    2040            0 :       CALL dbcsr_release(transform)
    2041            0 :       CALL dbcsr_distribution_release(dist_AB)
    2042            0 :       CALL dbcsr_distribution_release(dist_t)
    2043            0 :       DEALLOCATE (first_p_ABs_per_atom, retained_size, col_dist, AB_row_dist, row_offset, &
    2044            0 :                   fit_radius, needed_fit_atom, union_mask)
    2045            0 :       CALL timestop(handle)
    2046            0 :    END SUBROUTINE compute_auto_ri_d_lp
    2047              : 
    2048              : ! **************************************************************************************************
    2049              : !> \brief Extracts rhs(i,q) = d_{local_index(i),q} from one optimized atomic DBCSR column block.
    2050              : !> \param mat_rhs ...
    2051              : !> \param atom_index ...
    2052              : !> \param local_index ...
    2053              : !> \param row_offset ...
    2054              : !> \param rhs ...
    2055              : ! **************************************************************************************************
    2056            0 :    SUBROUTINE extract_atom_d_lp(mat_rhs, atom_index, local_index, row_offset, rhs)
    2057              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_rhs
    2058              :       INTEGER, INTENT(IN)                                :: atom_index
    2059              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: local_index, row_offset
    2060              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: rhs
    2061              : 
    2062              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'extract_atom_d_lp'
    2063              : 
    2064              :       INTEGER                                            :: handle, l, next_row, row
    2065              :       LOGICAL                                            :: found
    2066            0 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block
    2067              : 
    2068            0 :       CALL timeset(routineN, handle)
    2069              : 
    2070            0 :       rhs = 0.0_dp
    2071            0 :       row = 0
    2072            0 :       next_row = 1
    2073            0 :       NULLIFY (block)
    2074            0 :       DO l = 1, SIZE(rhs, 1)
    2075            0 :          DO WHILE (next_row <= SIZE(row_offset))
    2076            0 :             IF (row_offset(next_row) >= local_index(l)) EXIT
    2077            0 :             row = next_row
    2078            0 :             next_row = next_row + 1
    2079            0 :             CALL dbcsr_get_block_p(mat_rhs, row, atom_index, block, found)
    2080            0 :             IF (.NOT. found) NULLIFY (block)
    2081              :          END DO
    2082            0 :          IF (ASSOCIATED(block)) rhs(l, :) = block(local_index(l) - row_offset(row), :)
    2083              :       END DO
    2084              : 
    2085            0 :       CALL timestop(handle)
    2086            0 :    END SUBROUTINE extract_atom_d_lp
    2087              : 
    2088              : ! **************************************************************************************************
    2089              : !> \brief Solves Σ_l' D_ll' Z_l'q = d_lp for each optimized atomic column block. Small grids are
    2090              : !>        gathered within one process column and solved by a local Cholesky factorization.
    2091              : !> \param qs_env ...
    2092              : !> \param bs_env ...
    2093              : !> \param grid ...
    2094              : !> \param mat_rhs ...
    2095              : !> \param mat_z ...
    2096              : ! **************************************************************************************************
    2097            0 :    SUBROUTINE fit_auto_ri_z_lp(qs_env, bs_env, grid, mat_rhs, mat_z)
    2098              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2099              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2100              :       REAL(KIND=dp), INTENT(IN)                          :: grid(:, :)
    2101              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_rhs, mat_z
    2102              : 
    2103              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'fit_auto_ri_z_lp'
    2104              : 
    2105              :       INTEGER                                            :: atom_index, base, group_size, handle, &
    2106              :                                                             handle_gather, i, mypcol, n_big, &
    2107              :                                                             n_small, natom, ncol, ngrid, npcol, &
    2108              :                                                             slot
    2109            0 :       INTEGER, ALLOCATABLE :: all_index(:), ao_atoms(:), ao_col_start(:), big_list(:), &
    2110            0 :          grid_size(:), local_index(:), row_offset(:), small_list(:)
    2111            0 :       INTEGER, POINTER                                   :: row_size(:)
    2112            0 :       LOGICAL, ALLOCATABLE                               :: single_rank(:)
    2113            0 :       REAL(KIND=dp), ALLOCATABLE                         :: atom_rhs(:, :), buffer(:, :), &
    2114            0 :                                                             local_rhs(:, :), phi(:, :), radius(:)
    2115              :       TYPE(dbcsr_distribution_type)                      :: distribution
    2116              :       TYPE(mp_para_env_type), POINTER                    :: column_env, para_env
    2117            0 :       TYPE(qs_kind_type), POINTER                        :: qs_kinds(:)
    2118              : 
    2119            0 :       CALL timeset(routineN, handle)
    2120            0 :       CALL get_qs_env(qs_env, para_env=para_env, qs_kind_set=qs_kinds)
    2121            0 :       CALL dbcsr_get_info(mat_rhs, distribution=distribution, row_blk_size=row_size)
    2122            0 :       CALL dbcsr_distribution_get(distribution, mypcol=mypcol, npcols=npcol)
    2123            0 :       ALLOCATE (column_env)
    2124            0 :       CALL column_env%from_split(para_env, mypcol)
    2125            0 :       natom = bs_env%n_atom
    2126            0 :       ALLOCATE (radius(natom), all_index(SIZE(grid, 2)), row_offset(SIZE(row_size)))
    2127            0 :       CALL compute_auto_ri_grid_radii(bs_env, radius)
    2128              :       CALL classify_z_lp_atoms(bs_env, grid, radius, &
    2129              :                                bs_env%auto_ri%sizes_opt_RI, grid_size, &
    2130            0 :                                small_list, n_small, big_list, n_big, group_size)
    2131            0 :       ALLOCATE (single_rank(natom), source=.FALSE.)
    2132            0 :       single_rank(small_list(:n_small)) = .TRUE.
    2133            0 :       DO i = 1, SIZE(all_index)
    2134            0 :          all_index(i) = i
    2135              :       END DO
    2136            0 :       row_offset(1) = 0
    2137            0 :       DO i = 2, SIZE(row_size)
    2138            0 :          row_offset(i) = row_offset(i - 1) + row_size(i - 1)
    2139              :       END DO
    2140            0 :       DO base = mypcol + 1, natom, npcol*column_env%num_pe
    2141            0 :          CALL timeset(routineN//'_gather_rhs', handle_gather)
    2142            0 :          DO slot = 0, column_env%num_pe - 1
    2143            0 :             atom_index = base + slot*npcol
    2144            0 :             IF (atom_index > natom) EXIT
    2145            0 :             IF (.NOT. single_rank(atom_index)) CYCLE
    2146            0 :             ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2147            0 :             IF (ncol == 0) CYCLE
    2148            0 :             ALLOCATE (buffer(SIZE(grid, 2), ncol))
    2149            0 :             CALL extract_atom_d_lp(mat_rhs, atom_index, all_index, row_offset, buffer)
    2150            0 :             CALL column_env%sum(buffer, slot)
    2151            0 :             IF (column_env%mepos == slot) THEN
    2152            0 :                CALL MOVE_ALLOC(buffer, atom_rhs)
    2153              :             ELSE
    2154            0 :                DEALLOCATE (buffer)
    2155              :             END IF
    2156              :          END DO
    2157            0 :          CALL timestop(handle_gather)
    2158            0 :          atom_index = base + column_env%mepos*npcol
    2159            0 :          IF (atom_index > natom) CYCLE
    2160            0 :          IF (.NOT. single_rank(atom_index)) CYCLE
    2161            0 :          ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2162            0 :          IF (ncol == 0) CYCLE
    2163              :          CALL build_phi_on_sphere(bs_env, qs_kinds, grid, atom_index, &
    2164            0 :                                   radius(atom_index), local_index, ngrid, phi, ao_atoms, ao_col_start)
    2165            0 :          DEALLOCATE (ao_atoms, ao_col_start)
    2166            0 :          ALLOCATE (local_rhs(ngrid, ncol))
    2167            0 :          DO i = 1, ngrid
    2168            0 :             local_rhs(i, :) = atom_rhs(local_index(i), :)
    2169              :          END DO
    2170            0 :          DEALLOCATE (atom_rhs)
    2171            0 :          CALL solve_ri_rs_fit(phi, local_rhs, bs_env%ri_rs%tikhonov)
    2172              :          CALL store_Z_lP_columns(mat_z, local_rhs, local_index, ngrid, ncol, atom_index, &
    2173            0 :                                  row_size, row_offset, 0.0_dp)
    2174            0 :          DEALLOCATE (local_rhs, phi, local_index)
    2175              :       END DO
    2176            0 :       IF (n_big > 0) THEN
    2177              :          CALL fit_distributed_auto_ri_z_lp(qs_env, bs_env, grid, mat_rhs, mat_z, radius, &
    2178              :                                            big_list(:n_big), group_size, row_size, row_offset, &
    2179            0 :                                            all_index)
    2180              :       END IF
    2181            0 :       DEALLOCATE (radius, all_index, row_offset, single_rank, grid_size, small_list, big_list)
    2182            0 :       CALL column_env%free()
    2183            0 :       DEALLOCATE (column_env)
    2184            0 :       CALL dbcsr_finalize(mat_z)
    2185            0 :       CALL timestop(handle)
    2186            0 :    END SUBROUTINE fit_auto_ri_z_lp
    2187              : 
    2188              : ! **************************************************************************************************
    2189              : !> \brief Solves Σ_l' D_ll' Z_l'q = d_lp for large atomic grids with distributed Cholesky
    2190              : !>        factorization. Each d_lp block is gathered once and distributed over its assigned rank
    2191              : !>        group.
    2192              : !> \param qs_env ...
    2193              : !> \param bs_env ...
    2194              : !> \param grid ...
    2195              : !> \param mat_rhs ...
    2196              : !> \param mat_z ...
    2197              : !> \param radius ...
    2198              : !> \param atom_list ...
    2199              : !> \param group_size ...
    2200              : !> \param row_size ...
    2201              : !> \param row_offset ...
    2202              : !> \param all_index ...
    2203              : ! **************************************************************************************************
    2204            0 :    SUBROUTINE fit_distributed_auto_ri_z_lp(qs_env, bs_env, grid, mat_rhs, mat_z, radius, &
    2205            0 :                                            atom_list, group_size, row_size, row_offset, &
    2206            0 :                                            all_index)
    2207              :       TYPE(qs_environment_type), POINTER                 :: qs_env
    2208              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2209              :       REAL(KIND=dp), INTENT(IN)                          :: grid(:, :)
    2210              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: mat_rhs, mat_z
    2211              :       REAL(KIND=dp), INTENT(IN)                          :: radius(:)
    2212              :       INTEGER, INTENT(IN)                                :: atom_list(:), group_size, row_size(:), &
    2213              :                                                             row_offset(:), all_index(:)
    2214              : 
    2215              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'fit_distributed_auto_ri_z_lp'
    2216              : 
    2217              :       INTEGER                                            :: atom_index, base, handle, i, my_group, &
    2218              :                                                             ncol, ngrid, ngroups, root, slot
    2219            0 :       INTEGER, ALLOCATABLE                               :: ao_atoms(:), ao_col_start(:), &
    2220            0 :                                                             local_index(:)
    2221            0 :       REAL(KIND=dp), ALLOCATABLE                         :: atom_rhs(:, :), buffer(:, :), &
    2222            0 :                                                             local_rhs(:, :), phi(:, :)
    2223              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    2224              :       TYPE(mp_para_env_type), POINTER                    :: group_env, para_env
    2225            0 :       TYPE(qs_kind_type), POINTER                        :: qs_kinds(:)
    2226              : 
    2227            0 :       CALL timeset(routineN, handle)
    2228            0 :       CALL get_qs_env(qs_env, para_env=para_env, qs_kind_set=qs_kinds)
    2229            0 :       ngroups = MAX(1, para_env%num_pe/group_size)
    2230            0 :       my_group = MIN(para_env%mepos/group_size, ngroups - 1)
    2231            0 :       ALLOCATE (group_env)
    2232            0 :       CALL group_env%from_split(para_env, my_group)
    2233            0 :       NULLIFY (blacs_env)
    2234            0 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=group_env)
    2235            0 :       DO base = 1, SIZE(atom_list), ngroups
    2236            0 :          DO slot = 0, ngroups - 1
    2237            0 :             IF (base + slot > SIZE(atom_list)) EXIT
    2238            0 :             atom_index = atom_list(base + slot)
    2239            0 :             ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2240            0 :             IF (ncol == 0) CYCLE
    2241            0 :             root = slot*group_size
    2242            0 :             ALLOCATE (buffer(SIZE(grid, 2), ncol))
    2243            0 :             CALL extract_atom_d_lp(mat_rhs, atom_index, all_index, row_offset, buffer)
    2244            0 :             CALL para_env%sum(buffer, root)
    2245            0 :             IF (para_env%mepos == root) THEN
    2246            0 :                CALL MOVE_ALLOC(buffer, atom_rhs)
    2247              :             ELSE
    2248            0 :                DEALLOCATE (buffer)
    2249              :             END IF
    2250              :          END DO
    2251            0 :          IF (base + my_group > SIZE(atom_list)) CYCLE
    2252            0 :          atom_index = atom_list(base + my_group)
    2253            0 :          ncol = bs_env%auto_ri%sizes_opt_RI(atom_index)
    2254            0 :          IF (ncol == 0) CYCLE
    2255            0 :          IF (group_env%mepos /= 0) ALLOCATE (atom_rhs(SIZE(grid, 2), ncol))
    2256            0 :          CALL group_env%bcast(atom_rhs, 0)
    2257              :          CALL build_phi_on_sphere(bs_env, qs_kinds, grid, atom_index, &
    2258            0 :                                   radius(atom_index), local_index, ngrid, phi, ao_atoms, ao_col_start)
    2259            0 :          DEALLOCATE (ao_atoms, ao_col_start)
    2260            0 :          ALLOCATE (local_rhs(ngrid, ncol))
    2261            0 :          DO i = 1, ngrid
    2262            0 :             local_rhs(i, :) = atom_rhs(local_index(i), :)
    2263              :          END DO
    2264            0 :          DEALLOCATE (atom_rhs)
    2265            0 :          CALL solve_ri_rs_fit(phi, local_rhs, bs_env%ri_rs%tikhonov, group_env, blacs_env)
    2266            0 :          IF (group_env%mepos == 0) THEN
    2267              :             CALL store_Z_lP_columns(mat_z, local_rhs, local_index, ngrid, ncol, atom_index, &
    2268            0 :                                     row_size, row_offset, 0.0_dp)
    2269              :          END IF
    2270            0 :          DEALLOCATE (local_rhs, phi, local_index)
    2271              :       END DO
    2272            0 :       CALL cp_blacs_env_release(blacs_env)
    2273            0 :       CALL group_env%free()
    2274            0 :       DEALLOCATE (group_env)
    2275            0 :       CALL timestop(handle)
    2276            0 :    END SUBROUTINE fit_distributed_auto_ri_z_lp
    2277              : 
    2278              : ! **************************************************************************************************
    2279              : !> \brief Splits the atoms of the Z_lP solve into a single-rank list ("small", Phase A: LAPACK
    2280              : !>        dpotrf/dpotrs on one rank) and a distributed list ("big", Phase B: ScaLAPACK
    2281              : !>        pdpotrf/pdpotrs over a rank subgroup of size G), and sizes G.
    2282              : !>        Each atom's single-rank peak memory is estimated as
    2283              : !>          peak(P) = 8*n_local_grid(P)^2                (dense conditioned Gram matrix)
    2284              : !>                  + 8*n_local_grid(P)*n_ao_used(P)     (phi_local)
    2285              : !>                  + 8*n_local_grid(P)*n_RI(P)*(1+n_threads)   (d_lp + OMP partials)
    2286              : !>        and send atoms whose peak exceeds mem_safety * available-memory-per-proc to the
    2287              : !>        distributed path; G is auto-sized so the biggest atom's distributed Gram matrix slice
    2288              : !>        fits alongside the replicated phi_local + d_lp. G is floored by the ScaLAPACK 32-bit
    2289              : !>        index limit (a local block-cyclic slice of ~n_local_grid^2/G elements must stay below
    2290              : !>        2^31 or pdpotrf segfaults).
    2291              : !>        Records and reports the memory estimates before the standard or AUTO_RI solves.
    2292              : !> \param bs_env ...
    2293              : !> \param ri_rs_grid_points ...
    2294              : !> \param cutoff_ri_per_atom ...
    2295              : !> \param ri_blk_sizes per-atom ...
    2296              : !> \param n_local_grid_atom ...
    2297              : !> \param small_list ...
    2298              : !> \param n_small ...
    2299              : !> \param big_list ...
    2300              : !> \param n_big ...
    2301              : !> \param G ...
    2302              : !> \param periodic_images use minimum-image distances for the Gamma integration domain
    2303              : ! **************************************************************************************************
    2304           42 :    SUBROUTINE classify_z_lp_atoms(bs_env, ri_rs_grid_points, cutoff_ri_per_atom, ri_blk_sizes, &
    2305              :                                   n_local_grid_atom, small_list, n_small, big_list, n_big, G, periodic_images)
    2306              : 
    2307              : !$    USE OMP_LIB, ONLY: omp_get_max_threads
    2308              : 
    2309              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2310              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    2311              : 
    2312              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: cutoff_ri_per_atom
    2313              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: ri_blk_sizes
    2314              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: n_local_grid_atom, small_list, big_list
    2315              :       INTEGER, INTENT(OUT)                               :: n_small, n_big, G
    2316              :       LOGICAL, INTENT(IN), OPTIONAL :: periodic_images
    2317              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'classify_z_lp_atoms'
    2318              : 
    2319              :       INTEGER                                            :: handle
    2320              : 
    2321              :       ! Conservative fraction of measured available memory usable per rank for the Z_lP
    2322              :       REAL(KIND=dp), PARAMETER                           :: mem_safety = 0.8_dp
    2323              : 
    2324              :       ! ScaLAPACK/BLACS index the per-rank local block-cyclic slice (~n_local_grid^2/G
    2325              :       ! elements) with 32-bit integers; keep it safely below 2^31 or pdpotrf segfaults.
    2326              :       REAL(KIND=dp), PARAMETER                           :: scalapack_loc_limit = 2.0E9_dp
    2327              : 
    2328              :       INTEGER                                            :: G_atom, G_int32, l, n_ao_used_atom, &
    2329              :                                                             n_grid_total, n_local_grid, natom, &
    2330              :                                                             nthreads_cls, P_loop_atom
    2331              :       REAL(KIND=dp)                                      :: budget_bytes, cutoff_ri, dlp_bytes, &
    2332              :                                                             mem_avail_GB, ng, nri, peak_bytes, &
    2333              :                                                             phi_bytes
    2334              :       REAL(KIND=dp), DIMENSION(3)                        :: pos_P, displacement
    2335              :       LOGICAL :: sum_images
    2336              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2337           42 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2338              : 
    2339           42 :       CALL timeset(routineN, handle)
    2340           42 :       sum_images = .FALSE.
    2341           42 :       IF (PRESENT(periodic_images)) sum_images = periodic_images
    2342              : 
    2343           42 :       para_env => bs_env%para_env
    2344           42 :       particle_set => bs_env%ri_rs%particle_set
    2345           42 :       natom = bs_env%n_atom
    2346           42 :       n_grid_total = bs_env%ri_rs%n_grid_points
    2347           42 :       CPASSERT(SIZE(ri_rs_grid_points, 2) == n_grid_total)
    2348              : 
    2349              :       ! Per-atom sphere size: n_local_grid(P) = number of l with |r_l - R_P| <= cutoff_ri(P).
    2350              :       ! It sets both the Gram matrix memory footprint (n_local_grid^2) and the solve
    2351              :       ! cost (~n_local_grid^3), so it drives classification and the LPT load balancing.
    2352          210 :       ALLOCATE (n_local_grid_atom(natom), small_list(natom), big_list(natom))
    2353          142 :       DO P_loop_atom = 1, natom
    2354          400 :          pos_P(:) = particle_set(P_loop_atom)%r(:)
    2355          100 :          cutoff_ri = cutoff_ri_per_atom(P_loop_atom)
    2356          100 :          n_local_grid = 0
    2357        38088 :          DO l = 1, n_grid_total
    2358       151952 :             displacement = ri_rs_grid_points(:, l) - pos_P
    2359        37988 :             IF (sum_images) displacement = pbc(ri_rs_grid_points(:, l), pos_P, bs_env%ri_rs%cell)
    2360       152052 :             IF (NORM2(displacement) <= cutoff_ri) THEN
    2361        37850 :                n_local_grid = n_local_grid + 1
    2362              :             END IF
    2363              :          END DO
    2364          142 :          n_local_grid_atom(P_loop_atom) = n_local_grid
    2365              :       END DO
    2366              : 
    2367           42 :       nthreads_cls = 1
    2368           42 : !$    nthreads_cls = omp_get_max_threads()
    2369           42 :       CALL mp_mem_avail_per_rank_GB(bs_env%para_env, mem_avail_GB)  ! collective over all ranks
    2370           42 :       budget_bytes = mem_safety*mem_avail_GB*1.0E9_dp
    2371              : 
    2372           42 :       n_small = 0
    2373           42 :       n_big = 0
    2374           42 :       G = 1
    2375           42 :       G_atom = 1              ! max G a big atom needs (memory + ScaLAPACK int32 floor)
    2376           42 :       IF (mem_avail_GB <= 0.0_dp .AND. bs_env%unit_nr > 0) THEN
    2377            0 :          CPWARN("RI-RS Z_lP: no meminfo; single-rank solve for all atoms")
    2378              :       END IF
    2379          142 :       DO P_loop_atom = 1, natom
    2380          100 :          ng = REAL(n_local_grid_atom(P_loop_atom), dp)
    2381          100 :          nri = REAL(ri_blk_sizes(P_loop_atom), dp)
    2382              :          CALL get_n_ao_in_sphere(bs_env, P_loop_atom, &
    2383          100 :                                  cutoff_ri_per_atom(P_loop_atom), n_ao_used_atom, sum_images)
    2384              :          CALL record_fit_memory(bs_env%ri_rs, n_local_grid_atom(P_loop_atom), &
    2385          100 :                                 n_ao_used_atom, ri_blk_sizes(P_loop_atom), peak_bytes)
    2386          142 :          IF (mem_avail_GB <= 0.0_dp .OR. peak_bytes <= budget_bytes) THEN
    2387          100 :             n_small = n_small + 1
    2388          100 :             small_list(n_small) = P_loop_atom
    2389              :          ELSE
    2390            0 :             n_big = n_big + 1
    2391            0 :             big_list(n_big) = P_loop_atom
    2392            0 :             phi_bytes = 8.0_dp*ng*REAL(n_ao_used_atom, dp)
    2393            0 :             dlp_bytes = 8.0_dp*ng*nri*REAL(1 + nthreads_cls, dp)
    2394              :             ! Bound both memory and ScaLAPACK's 32-bit local indexing.
    2395            0 :             G_int32 = CEILING(ng*ng/scalapack_loc_limit)
    2396              :             G_atom = MAX(G_atom, G_int32, &
    2397            0 :                          CEILING(8.0_dp*ng*ng/MAX(budget_bytes - phi_bytes - dlp_bytes, 1.0_dp)))
    2398              :          END IF
    2399              :       END DO
    2400           42 :       CALL print_fit_memory_estimate(bs_env, mem_avail_GB)
    2401           42 :       IF (n_big > 0) THEN
    2402            0 :          IF (G_atom > para_env%num_pe) THEN
    2403              :             CALL cp_abort(__LOCATION__, &
    2404              :                           "RI-RS Z_lP: an atom is too large to fit even when "// &
    2405              :                           "distributed over all ranks. Add nodes, use fewer MPI ranks "// &
    2406              :                           "per node, lower CUTOFF_RADIUS_RI, or raise EPS_FILTER "// &
    2407            0 :                           "for more grid screening.")
    2408              :          END IF
    2409            0 :          G = MIN(MAX(G_atom, 2), para_env%num_pe)
    2410              :       END IF
    2411              : 
    2412           42 :       CALL timestop(handle)
    2413              : 
    2414           42 :    END SUBROUTINE classify_z_lp_atoms
    2415              : 
    2416              : ! **************************************************************************************************
    2417              : !> \brief Number of AO basis functions that can be non-zero inside the RI-RS integration sphere
    2418              : !> \param bs_env ...
    2419              : !> \param atom_P ...
    2420              : !> \param cutoff_ri ...
    2421              : !> \param n_ao_used ...
    2422              : !> \param periodic_images use minimum-image distances to the Gamma integration sphere
    2423              : ! **************************************************************************************************
    2424          100 :    SUBROUTINE get_n_ao_in_sphere(bs_env, atom_P, cutoff_ri, n_ao_used, periodic_images)
    2425              : 
    2426              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2427              :       INTEGER, INTENT(IN)                                :: atom_P
    2428              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_ri
    2429              :       INTEGER, INTENT(OUT)                               :: n_ao_used
    2430              :       LOGICAL, INTENT(IN)                                :: periodic_images
    2431              : 
    2432              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_n_ao_in_sphere'
    2433              : 
    2434              :       INTEGER                                            :: handle, ri_atom
    2435              :       REAL(KIND=dp)                                      :: displacement(3)
    2436          100 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2437              : 
    2438          100 :       CALL timeset(routineN, handle)
    2439              : 
    2440          100 :       particle_set => bs_env%ri_rs%particle_set
    2441          100 :       n_ao_used = 0
    2442          348 :       DO ri_atom = 1, bs_env%n_atom
    2443          992 :          displacement = particle_set(ri_atom)%r - particle_set(atom_P)%r
    2444          248 :          IF (periodic_images) displacement = pbc(particle_set(ri_atom)%r, particle_set(atom_P)%r, bs_env%ri_rs%cell)
    2445          992 :          IF (NORM2(displacement) > &
    2446              :              bs_env%ri_rs%radius_ao_per_atom(ri_atom) + cutoff_ri) CYCLE
    2447              :          n_ao_used = n_ao_used + bs_env%i_ao_end_from_atom(ri_atom) - &
    2448          348 :                      bs_env%i_ao_start_from_atom(ri_atom) + 1
    2449              :       END DO
    2450              : 
    2451          100 :       CALL timestop(handle)
    2452              : 
    2453          100 :    END SUBROUTINE get_n_ao_in_sphere
    2454              : 
    2455              : ! **************************************************************************************************
    2456              : !> \brief Builds the sphere-local AO matrix phi_local(l, μ) = ϕ_μ(r_l) for one RI atom P
    2457              : !> \param bs_env ...
    2458              : !> \param qs_kind_set ...
    2459              : !> \param ri_rs_grid_points ...
    2460              : !> \param atom_P ...
    2461              : !> \param cutoff_ri ...
    2462              : !> \param local_grid_idx ...
    2463              : !> \param n_local_grid ...
    2464              : !> \param phi_local ...
    2465              : !> \param ao_atoms global indices of the active AO atoms
    2466              : !> \param ao_col_start local phi column starts, including the terminal column offset
    2467              : !> \param center ...
    2468              : !> \param periodic_images select Gamma image-summed AO values and minimum-image sphere distances
    2469              : !> \param ao_nonzero exact nonzero AO support on the retained grid rows
    2470              : ! **************************************************************************************************
    2471           54 :    SUBROUTINE build_phi_on_sphere(bs_env, qs_kind_set, ri_rs_grid_points, &
    2472              :                                   atom_P, cutoff_ri, local_grid_idx, n_local_grid, &
    2473              :                                   phi_local, ao_atoms, ao_col_start, center, periodic_images, ao_nonzero)
    2474              : 
    2475              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2476              :       TYPE(qs_kind_type), DIMENSION(:), POINTER          :: qs_kind_set
    2477              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: ri_rs_grid_points
    2478              :       INTEGER, INTENT(IN)                                :: atom_P
    2479              :       REAL(KIND=dp), INTENT(IN)                          :: cutoff_ri
    2480              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: local_grid_idx
    2481              :       INTEGER, INTENT(OUT)                               :: n_local_grid
    2482              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
    2483              :          INTENT(OUT)                                     :: phi_local
    2484              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: ao_atoms, ao_col_start
    2485              :       REAL(KIND=dp), DIMENSION(3), INTENT(IN), OPTIONAL  :: center
    2486              :       LOGICAL, INTENT(IN), OPTIONAL                      :: periodic_images
    2487              :       LOGICAL, ALLOCATABLE, INTENT(OUT), OPTIONAL        :: ao_nonzero(:, :)
    2488              : 
    2489              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_phi_on_sphere'
    2490              : 
    2491              :       INTEGER                                            :: col_end, col_start, handle, iactive, j, &
    2492              :                                                             k, l, loc_idx, n_active, n_ao_used, &
    2493              :                                                             n_grid_total, n_keep, ri_atom
    2494           54 :       INTEGER, ALLOCATABLE                               :: atom_candidates(:)
    2495              :       LOGICAL                                            :: sum_images
    2496              :       REAL(KIND=dp)                                      :: d_sP, dist, r2_threshold
    2497           54 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: w_pt
    2498           54 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: phi_keep, sphere_grid
    2499              :       REAL(KIND=dp), DIMENSION(3)                        :: displacement, pos_P
    2500              :       TYPE(cell_type), POINTER                           :: cell
    2501           54 :       TYPE(particle_type), DIMENSION(:), POINTER         :: particle_set
    2502              : 
    2503           54 :       CALL timeset(routineN, handle)
    2504           54 :       sum_images = .FALSE.
    2505           54 :       IF (PRESENT(periodic_images)) sum_images = periodic_images
    2506              : 
    2507           54 :       cell => bs_env%ri_rs%cell
    2508           54 :       particle_set => bs_env%ri_rs%particle_set
    2509              :       ! AUTO_RI may pass only the global grid points needed for the current atom block.
    2510           54 :       n_grid_total = SIZE(ri_rs_grid_points, 2)
    2511           54 :       IF (PRESENT(center)) THEN
    2512            4 :          pos_P(:) = center(:)
    2513              :       ELSE
    2514          200 :          pos_P(:) = particle_set(atom_P)%r(:)
    2515              :       END IF
    2516              : 
    2517           54 :       n_local_grid = 0
    2518        19174 :       DO l = 1, n_grid_total
    2519        76480 :          displacement = ri_rs_grid_points(:, l) - pos_P
    2520        19120 :          IF (sum_images) displacement = pbc(ri_rs_grid_points(:, l), pos_P, cell)
    2521        76480 :          dist = NORM2(displacement)
    2522        19174 :          IF (dist <= cutoff_ri) n_local_grid = n_local_grid + 1
    2523              :       END DO
    2524              : 
    2525          162 :       ALLOCATE (local_grid_idx(n_local_grid))
    2526              : 
    2527           54 :       n_local_grid = 0
    2528        19174 :       DO l = 1, n_grid_total
    2529        76480 :          displacement = ri_rs_grid_points(:, l) - pos_P
    2530        19120 :          IF (sum_images) displacement = pbc(ri_rs_grid_points(:, l), pos_P, cell)
    2531        76480 :          dist = NORM2(displacement)
    2532        19174 :          IF (dist <= cutoff_ri) THEN
    2533        19051 :             n_local_grid = n_local_grid + 1
    2534        19051 :             local_grid_idx(n_local_grid) = l
    2535              :          END IF
    2536              :       END DO
    2537              : 
    2538          162 :       ALLOCATE (sphere_grid(3, n_local_grid))
    2539        19105 :       DO loc_idx = 1, n_local_grid
    2540        76258 :          sphere_grid(:, loc_idx) = ri_rs_grid_points(:, local_grid_idx(loc_idx))
    2541              :       END DO
    2542              : 
    2543              :       ! Keep atoms in global order; phi columns are contiguous within each active atom.
    2544          162 :       ALLOCATE (atom_candidates(bs_env%n_atom))
    2545           54 :       n_active = 0
    2546          190 :       DO ri_atom = 1, bs_env%n_atom
    2547          544 :          displacement = particle_set(ri_atom)%r - pos_P
    2548          136 :          IF (sum_images) displacement = pbc(particle_set(ri_atom)%r, pos_P, cell)
    2549          544 :          d_sP = NORM2(displacement)
    2550          136 :          IF (d_sP > bs_env%ri_rs%radius_ao_per_atom(ri_atom) + cutoff_ri) CYCLE
    2551          136 :          IF (bs_env%i_ao_end_from_atom(ri_atom) < bs_env%i_ao_start_from_atom(ri_atom)) CYCLE
    2552          136 :          n_active = n_active + 1
    2553          190 :          atom_candidates(n_active) = ri_atom
    2554              :       END DO
    2555          298 :       ALLOCATE (ao_atoms(n_active), SOURCE=atom_candidates(:n_active))
    2556           54 :       DEALLOCATE (atom_candidates)
    2557          162 :       ALLOCATE (ao_col_start(n_active + 1))
    2558           54 :       ao_col_start(1) = 1
    2559          190 :       DO iactive = 1, n_active
    2560          136 :          ri_atom = ao_atoms(iactive)
    2561              :          ao_col_start(iactive + 1) = ao_col_start(iactive) + &
    2562          190 :                                      bs_env%i_ao_end_from_atom(ri_atom) - bs_env%i_ao_start_from_atom(ri_atom) + 1
    2563              :       END DO
    2564           54 :       n_ao_used = ao_col_start(n_active + 1) - 1
    2565          216 :       ALLOCATE (phi_local(n_local_grid, n_ao_used), SOURCE=0.0_dp)
    2566              : 
    2567          190 :       DO iactive = 1, n_active
    2568          136 :          ri_atom = ao_atoms(iactive)
    2569          136 :          col_start = ao_col_start(iactive)
    2570          136 :          col_end = ao_col_start(iactive + 1) - 1
    2571              :          ! A positive CUTOFF_RADIUS_RI_AO overrides the per-atom Gaussian radius
    2572              :          ! with a user-defined hard cutoff.
    2573          136 :          IF (.NOT. sum_images .AND. bs_env%ri_rs%cutoff_radius_ri_ao > 0.0_dp) THEN
    2574            9 :             r2_threshold = bs_env%ri_rs%cutoff_radius_ri_ao**2
    2575              :          ELSE
    2576          127 :             r2_threshold = bs_env%ri_rs%radius_ao_per_atom(ri_atom)**2
    2577              :          END IF
    2578              : 
    2579          190 :          IF (sum_images) THEN
    2580              :             CALL evaluate_ao_basis_on_points(phi_local(:, col_start:col_end), sphere_grid, &
    2581              :                                              bs_env%basis_set_AO( &
    2582              :                                              particle_set(ri_atom)%atomic_kind%kind_number)%gto_basis_set, &
    2583              :                                              particle_set(ri_atom)%r, cell, &
    2584            0 :                                              cutoff_squared=r2_threshold, periodic_images=.TRUE.)
    2585              :          ELSE
    2586              :             CALL evaluate_ao_on_points(phi_local(:, col_start:col_end), sphere_grid, &
    2587              :                                        ri_atom, particle_set, qs_kind_set, cell, &
    2588          136 :                                        cutoff_squared=r2_threshold)
    2589              :          END IF
    2590              :       END DO
    2591              : 
    2592           54 :       DEALLOCATE (sphere_grid)
    2593              : 
    2594           54 :       IF (.NOT. sum_images .AND. n_local_grid > 0) THEN
    2595          162 :          ALLOCATE (w_pt(n_local_grid))
    2596              :          !$OMP PARALLEL DO DEFAULT(NONE) &
    2597              :          !$OMP SHARED(n_local_grid, n_ao_used, phi_local, w_pt) &
    2598           54 :          !$OMP PRIVATE(l, j) SCHEDULE(STATIC)
    2599              :          DO l = 1, n_local_grid
    2600              :             w_pt(l) = 0.0_dp
    2601              :             DO j = 1, n_ao_used
    2602              :                w_pt(l) = MAX(w_pt(l), ABS(phi_local(l, j)))
    2603              :             END DO
    2604              :          END DO
    2605              :          !$OMP END PARALLEL DO
    2606        19105 :          n_keep = COUNT(w_pt > bs_env%eps_filter)
    2607           54 :          IF (n_keep < n_local_grid) THEN
    2608           24 :             ALLOCATE (phi_keep(n_keep, n_ao_used))
    2609            6 :             k = 0
    2610         2601 :             DO l = 1, n_local_grid
    2611         2601 :                IF (w_pt(l) > bs_env%eps_filter) THEN
    2612         2151 :                   k = k + 1
    2613        17208 :                   phi_keep(k, :) = phi_local(l, :)
    2614         2151 :                   local_grid_idx(k) = local_grid_idx(l)
    2615              :                END IF
    2616              :             END DO
    2617            6 :             CALL MOVE_ALLOC(phi_keep, phi_local)
    2618            6 :             n_local_grid = n_keep
    2619              :          END IF
    2620           54 :          DEALLOCATE (w_pt)
    2621              :       END IF
    2622              : 
    2623           54 :       IF (PRESENT(ao_nonzero)) THEN
    2624              :          ! Build support on the final retained rows, once for all RHS contractions using phi.
    2625          216 :          ALLOCATE (ao_nonzero(n_local_grid, n_active), SOURCE=.FALSE.)
    2626              :          !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) &
    2627              :          !$OMP SHARED(n_active, n_local_grid, ao_col_start, phi_local, ao_nonzero) &
    2628           54 :          !$OMP PRIVATE(iactive, j, l)
    2629              :          DO iactive = 1, n_active
    2630              :             DO j = ao_col_start(iactive), ao_col_start(iactive + 1) - 1
    2631              :                DO l = 1, n_local_grid
    2632              :                   ao_nonzero(l, iactive) = ao_nonzero(l, iactive) .OR. phi_local(l, j) /= 0.0_dp
    2633              :                END DO
    2634              :             END DO
    2635              :          END DO
    2636              :          !$OMP END PARALLEL DO
    2637              :       END IF
    2638              : 
    2639           54 :       CALL timestop(handle)
    2640              : 
    2641          108 :    END SUBROUTINE build_phi_on_sphere
    2642              : 
    2643              : ! **************************************************************************************************
    2644              : !> \brief Computes a three-center integral block directly in AUTO_RI contraction columns.
    2645              : !>
    2646              : !>        For each angular momentum l, the primitive RI Gaussians on the atoms contributing
    2647              : !>        to one optimized function are collected before the contraction
    2648              : !>
    2649              : !>          (μν|p) = Σ_P (μν|P) U_Pp .
    2650              : !>
    2651              : !>        This avoids constructing and retaining the complete reference-basis block (μν|P).
    2652              : !> \param int_3c ...
    2653              : !> \param ctx ...
    2654              : !> \param ws ...
    2655              : !> \param atom_j ...
    2656              : !> \param atom_k ...
    2657              : !> \param atom_i ...
    2658              : !> \param transform ...
    2659              : !> \param transform_row ...
    2660              : !> \param screened ...
    2661              : ! **************************************************************************************************
    2662          108 :    SUBROUTINE build_3c_integral_block_auto_ri_ctx(int_3c, ctx, ws, atom_j, atom_k, atom_i, &
    2663           36 :                                                   transform, transform_row, screened)
    2664              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT)   :: int_3c
    2665              :       TYPE(gw_3c_ctx_type), INTENT(IN)                   :: ctx
    2666              :       TYPE(gw_3c_ws_type), INTENT(INOUT)                 :: ws
    2667              :       INTEGER, INTENT(IN)                                :: atom_j, atom_k, atom_i
    2668              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: transform
    2669              :       INTEGER, INTENT(IN)                                :: transform_row
    2670              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: screened
    2671              : 
    2672              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_3c_integral_block_auto_ri_ctx'
    2673              : 
    2674              :       INTEGER :: handle_contract, handle_eri, ikind, iset, jkind, jset, kkind, kset, l, ncoi, &
    2675              :          ncoj, ncok, ncol, npgf_group, nseti, nsetj, nsetk, primitive_first, sgfi, sgfj, sgfk
    2676           36 :       INTEGER, DIMENSION(:), POINTER                     :: lmax_i, lmax_j, lmax_k, lmin_i, lmin_j, &
    2677           36 :                                                             lmin_k, npgfi, npgfj, npgfk, nsgfi, &
    2678           36 :                                                             nsgfj, nsgfk
    2679           36 :       INTEGER, DIMENSION(:, :), POINTER                  :: first_sgf_i, first_sgf_j, first_sgf_k
    2680              :       REAL(KIND=dp)                                      :: dij, dik, djk, group_radius, &
    2681              :                                                             kind_radius_i, kind_radius_j, &
    2682              :                                                             kind_radius_k, sijk_ext
    2683           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: rpgf_group, zet_group
    2684           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: spi_group
    2685           36 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)     :: sijk, sijk_contr
    2686              :       REAL(KIND=dp), DIMENSION(3)                        :: ri, rij, rik, rj, rjk, rk
    2687           36 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: set_radius_i, set_radius_j, set_radius_k
    2688           36 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: rpgf_i, rpgf_j, rpgf_k, zeti, zetj, zetk
    2689              : 
    2690           36 :       IF (PRESENT(screened)) screened = .FALSE.
    2691           36 :       ri = pbc(ctx%particle_set(atom_i)%r(1:3), ctx%cell)
    2692           36 :       rj = pbc(ctx%particle_set(atom_j)%r(1:3), ctx%cell)
    2693           36 :       rk = pbc(ctx%particle_set(atom_k)%r(1:3), ctx%cell)
    2694          144 :       rjk = rk - rj
    2695          144 :       rij = rj - ri
    2696          144 :       rik = rk - ri
    2697          144 :       djk = NORM2(rjk)
    2698          144 :       dij = NORM2(rij)
    2699          144 :       dik = NORM2(rik)
    2700              : 
    2701           36 :       ikind = ctx%kind_of(atom_i)
    2702           36 :       jkind = ctx%kind_of(atom_j)
    2703           36 :       kkind = ctx%kind_of(atom_k)
    2704              :       CALL get_gto_basis_set(ctx%basis_i(ikind)%gto_basis_set, first_sgf=first_sgf_i, &
    2705              :                              lmax=lmax_i, lmin=lmin_i, npgf=npgfi, nset=nseti, &
    2706              :                              nsgf_set=nsgfi, pgf_radius=rpgf_i, set_radius=set_radius_i, &
    2707           36 :                              zet=zeti, kind_radius=kind_radius_i)
    2708              :       CALL get_gto_basis_set(ctx%basis_j(jkind)%gto_basis_set, first_sgf=first_sgf_j, &
    2709              :                              lmax=lmax_j, lmin=lmin_j, npgf=npgfj, nset=nsetj, &
    2710              :                              nsgf_set=nsgfj, pgf_radius=rpgf_j, set_radius=set_radius_j, &
    2711           36 :                              zet=zetj, kind_radius=kind_radius_j)
    2712              :       CALL get_gto_basis_set(ctx%basis_k(kkind)%gto_basis_set, first_sgf=first_sgf_k, &
    2713              :                              lmax=lmax_k, lmin=lmin_k, npgf=npgfk, nset=nsetk, &
    2714              :                              nsgf_set=nsgfk, pgf_radius=rpgf_k, set_radius=set_radius_k, &
    2715           36 :                              zet=zetk, kind_radius=kind_radius_k)
    2716              : 
    2717              :       IF (kind_radius_j + kind_radius_i + ctx%dr_ij < dij .OR. &
    2718           36 :           kind_radius_j + kind_radius_k + ctx%dr_jk < djk .OR. &
    2719              :           kind_radius_k + kind_radius_i + ctx%dr_ik < dik) THEN
    2720            0 :          IF (PRESENT(screened)) screened = .TRUE.
    2721            0 :          RETURN
    2722              :       END IF
    2723              : 
    2724           36 :       ncol = SIZE(transform, 2)
    2725           36 :       CPASSERT(SIZE(int_3c, 3) == ncol)
    2726          252 :       DO l = 0, ctx%maxli
    2727          216 :          npgf_group = 0
    2728          216 :          group_radius = 0.0_dp
    2729         4392 :          DO iset = 1, nseti
    2730         4176 :             IF (lmin_i(iset) /= l .OR. lmax_i(iset) /= l) CYCLE
    2731          696 :             npgf_group = npgf_group + npgfi(iset)
    2732         4392 :             group_radius = MAX(group_radius, set_radius_i(iset))
    2733              :          END DO
    2734          216 :          IF (npgf_group == 0) CYCLE
    2735          192 :          ncoi = npgf_group*ncoset(l)
    2736          768 :          ALLOCATE (zet_group(npgf_group), rpgf_group(npgf_group))
    2737          768 :          ALLOCATE (spi_group(ncoi, ncol), source=0.0_dp)
    2738          192 :          primitive_first = 1
    2739         4008 :          DO iset = 1, nseti
    2740         3816 :             IF (lmin_i(iset) /= l .OR. lmax_i(iset) /= l) CYCLE
    2741              :             zet_group(primitive_first:primitive_first + npgfi(iset) - 1) = &
    2742         1392 :                zeti(1:npgfi(iset), iset)
    2743              :             rpgf_group(primitive_first:primitive_first + npgfi(iset) - 1) = &
    2744         1392 :                rpgf_i(1:npgfi(iset), iset)
    2745          696 :             sgfi = first_sgf_i(1, iset)
    2746              :             spi_group((primitive_first - 1)*ncoset(l) + 1: &
    2747              :                       (primitive_first + npgfi(iset) - 1)*ncoset(l), :) = &
    2748          696 :                MATMUL(ctx%spi(iset, ikind)%array, &
    2749              :                       transform(transform_row + sgfi - 1: &
    2750      1437180 :                                 transform_row + sgfi + nsgfi(iset) - 2, :))
    2751         4008 :             primitive_first = primitive_first + npgfi(iset)
    2752              :          END DO
    2753              : 
    2754          480 :          DO jset = 1, nsetj
    2755          288 :             IF (set_radius_j(jset) + group_radius + ctx%dr_ij < dij) CYCLE
    2756          832 :             DO kset = 1, nsetk
    2757          352 :                IF (set_radius_j(jset) + set_radius_k(kset) + ctx%dr_jk < djk) CYCLE
    2758          352 :                IF (set_radius_k(kset) + group_radius + ctx%dr_ik < dik) CYCLE
    2759          352 :                ncoj = npgfj(jset)*ncoset(lmax_j(jset))
    2760          352 :                ncok = npgfk(kset)*ncoset(lmax_k(kset))
    2761          352 :                sgfj = first_sgf_j(1, jset)
    2762          352 :                sgfk = first_sgf_k(1, kset)
    2763          352 :                IF (ncoj*ncok*ncoi <= 0) CYCLE
    2764         1760 :                ALLOCATE (sijk(ncoj, ncok, ncoi), source=0.0_dp)
    2765          352 :                CALL timeset(routineN//'_eri', handle_eri)
    2766              :                CALL eri_3center(sijk, &
    2767              :                                 lmin_j(jset), lmax_j(jset), npgfj(jset), zetj(:, jset), &
    2768              :                                 rpgf_j(:, jset), rj, &
    2769              :                                 lmin_k(kset), lmax_k(kset), npgfk(kset), zetk(:, kset), &
    2770              :                                 rpgf_k(:, kset), rk, l, l, npgf_group, zet_group, &
    2771              :                                 rpgf_group, ri, djk, dij, dik, ws%lib, ctx%potential_parameter, &
    2772          352 :                                 int_abc_ext=sijk_ext)
    2773          352 :                CALL timestop(handle_eri)
    2774         1760 :                ALLOCATE (sijk_contr(nsgfj(jset), nsgfk(kset), ncol))
    2775          352 :                CALL timeset(routineN//'_contract', handle_contract)
    2776              :                CALL abc_contract_xsmm(sijk_contr, sijk, ctx%tspj(jset, jkind)%array, &
    2777              :                                       ctx%spk(kset, kkind)%array, spi_group, ncoj, ncok, ncoi, &
    2778          352 :                                       nsgfj(jset), nsgfk(kset), ncol, ws%cpp_buffer, ws%ccp_buffer)
    2779          352 :                CALL timestop(handle_contract)
    2780          352 :                DEALLOCATE (sijk)
    2781              :                int_3c(sgfj:sgfj + nsgfj(jset) - 1, sgfk:sgfk + nsgfk(kset) - 1, :) = &
    2782              :                   int_3c(sgfj:sgfj + nsgfj(jset) - 1, sgfk:sgfk + nsgfk(kset) - 1, :) + &
    2783        14476 :                   sijk_contr
    2784         1344 :                DEALLOCATE (sijk_contr)
    2785              :             END DO
    2786              :          END DO
    2787          252 :          DEALLOCATE (zet_group, rpgf_group, spi_group)
    2788              :       END DO
    2789              : 
    2790           72 :    END SUBROUTINE build_3c_integral_block_auto_ri_ctx
    2791              : 
    2792              : ! **************************************************************************************************
    2793              : !> \brief Returns the reference RI basis size of one atom.
    2794              : !> \param bs_env ...
    2795              : !> \param iatom ...
    2796              : !> \return ...
    2797              : ! **************************************************************************************************
    2798            9 :    INTEGER FUNCTION get_ref_RI_size(bs_env, iatom) RESULT(n)
    2799              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2800              :       INTEGER, INTENT(IN)                                :: iatom
    2801              : 
    2802              :       INTEGER                                            :: ikind
    2803              : 
    2804            9 :       ikind = bs_env%ri_rs%particle_set(iatom)%atomic_kind%kind_number
    2805            9 :       n = bs_env%basis_set_RI(ikind)%gto_basis_set%nsgf
    2806              : 
    2807            9 :    END FUNCTION get_ref_RI_size
    2808              : 
    2809              : ! **************************************************************************************************
    2810              : !> \brief Record the dense Gram, phi and threaded RHS memory model for an RI-RS fit.
    2811              : !>        The single-rank estimate is an upper bound for a distributed Gram solve.
    2812              : !> \param ri_rs owned fitting statistics, reset with the representation
    2813              : !> \param n_grid number of fitting grid rows, before screening when obtained by classification
    2814              : !> \param n_ao number of AO columns
    2815              : !> \param n_ri number of RHS columns
    2816              : !> \param peak_bytes optional single-rank estimate used by solver classification
    2817              : ! **************************************************************************************************
    2818          104 :    SUBROUTINE record_fit_memory(ri_rs, n_grid, n_ao, n_ri, peak_bytes)
    2819              : !$    USE OMP_LIB, ONLY: omp_get_max_threads
    2820              :       TYPE(ri_rs_env), INTENT(INOUT)                     :: ri_rs
    2821              :       INTEGER, INTENT(IN)                                :: n_grid, n_ao, n_ri
    2822              :       REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: peak_bytes
    2823              : 
    2824              :       INTEGER                                            :: nthreads
    2825              :       REAL(KIND=dp)                                      :: ng, peak
    2826              : 
    2827          104 :       nthreads = 1
    2828          104 : !$    nthreads = omp_get_max_threads()
    2829          104 :       ng = REAL(n_grid, dp)
    2830              :       peak = 8.0_dp*ng*ng + 8.0_dp*ng*REAL(n_ao, dp) + &
    2831          104 :              8.0_dp*ng*REAL(n_ri, dp)*REAL(1 + nthreads, dp)
    2832          104 :       ri_rs%fit_max_grid = MAX(ri_rs%fit_max_grid, n_grid)
    2833          104 :       ri_rs%fit_peak_bytes = MAX(ri_rs%fit_peak_bytes, peak)
    2834          104 :       IF (PRESENT(peak_bytes)) peak_bytes = peak
    2835          104 :    END SUBROUTINE record_fit_memory
    2836              : 
    2837              : ! **************************************************************************************************
    2838              : !> \brief Prints the fitting memory bound before allocating the local fitting RHS and Gram matrix.
    2839              : !> \param bs_env recorded fitting sizes and output unit
    2840              : !> \param mem_avail_GB available memory per rank, measured collectively by the caller
    2841              : ! **************************************************************************************************
    2842           46 :    SUBROUTINE print_fit_memory_estimate(bs_env, mem_avail_GB)
    2843              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
    2844              :       REAL(KIND=dp), INTENT(IN)                          :: mem_avail_GB
    2845              : 
    2846              :       CHARACTER(LEN=max_line_length)                     :: msg
    2847              :       REAL(KIND=dp)                                      :: mem_gram_GB, mem_Zlp_peak_GB
    2848              : 
    2849           46 :       mem_gram_GB = 8.0_dp*REAL(bs_env%ri_rs%fit_max_grid, dp)**2*1.0E-9_dp
    2850           46 :       mem_Zlp_peak_GB = bs_env%ri_rs%fit_peak_bytes*1.0E-9_dp
    2851           46 :       IF (bs_env%unit_nr > 0) THEN
    2852           23 :          WRITE (bs_env%unit_nr, '(T2,A)') 'RI-RS fitting memory estimate per MPI process:'
    2853              :          WRITE (bs_env%unit_nr, '(T4,A,F37.2,A)') &
    2854           23 :             'Available memory per process (system)', mem_avail_GB, ' GB'
    2855              :          WRITE (bs_env%unit_nr, '(T4,A,F17.2,A)') &
    2856           23 :             'Required for Z_lP fit (Gram+phi+RHS, single-rank bound)', mem_Zlp_peak_GB, ' GB'
    2857              :          WRITE (bs_env%unit_nr, '(T4,A,T69,I12)') &
    2858           23 :             'Largest fitting-grid number of grid points:', bs_env%ri_rs%fit_max_grid
    2859              :          WRITE (bs_env%unit_nr, '(T4,A,T69,F9.2,A)') &
    2860           23 :             'Largest dense Gram matrix:', mem_gram_GB, ' GB'
    2861           23 :          CALL m_flush(bs_env%unit_nr)
    2862              :       END IF
    2863              : 
    2864           46 :       IF (mem_avail_GB > 0.0_dp .AND. mem_Zlp_peak_GB > mem_avail_GB) THEN
    2865              :          WRITE (msg, '(A,F0.2,A,F0.2,A)') &
    2866            0 :             "The estimated peak memory of the Z_lP solve, ", mem_Zlp_peak_GB, &
    2867            0 :             " GB per process, exceeds the available ", mem_avail_GB, &
    2868              :             " GB. The dense Gram matrix scales as n_local_grid^2; "// &
    2869              :             "the estimate assumes a single-rank solve. Localized fits may distribute the Gram "// &
    2870              :             "matrix over a ScaLAPACK subgroup. Use fewer MPI ranks per node if more memory "// &
    2871            0 :             "per rank is needed."
    2872            0 :          CPWARN(TRIM(msg))
    2873              :       END IF
    2874           46 :    END SUBROUTINE print_fit_memory_estimate
    2875              : 
    2876              : END MODULE ri_rs_setup
        

Generated by: LCOV version 2.0-1