LCOV - code coverage report
Current view: top level - src - qs_kpoint_operators.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:123285f) Lines: 94.1 % 1039 978
Test Date: 2026-11-04 00:58:17 Functions: 80.4 % 46 37

            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 Assembly of real and complex k-point operators from real-space DBCSR matrices.
      10              : !>        Outputs use the distribution of the k-point group; real operators transfer one part only.
      11              : !>        The module splits this work into a start step and a finish step.
      12              : !>        One process can start many operators before it finishes any of them.
      13              : !>        The STANDARD k-point driver uses this order.
      14              : !>        The routines start, finish and get are collective over the
      15              : !>        environment-wide communicator. Every rank must call the same
      16              : !>        sequence of service calls.
      17              : !>        A context references its host kpoint environment without owning it,
      18              : !>        so it must not outlive that environment.
      19              : !>        An explicitly prepared, immutable operator batch can instead be built
      20              : !>        from bounded context-owned real-space snapshots inside each group.
      21              : !>        The public round and output-ownership contracts stay the same.
      22              : !>        Start/finish/get/prepare are not thread safe: serialize them, because their collective
      23              : !>        communication tolerates no concurrent service call. A separate
      24              : !>        context per thread only keeps the scratch from aliasing.
      25              : ! **************************************************************************************************
      26              : MODULE qs_kpoint_operators
      27              :    USE cp_cfm_basic_linalg,             ONLY: cp_cfm_scale_and_add_fm
      28              :    USE cp_cfm_types,                    ONLY: cp_cfm_type
      29              :    USE cp_dbcsr_api,                    ONLY: &
      30              :         dbcsr_create, dbcsr_deallocate_matrix, dbcsr_get_info, dbcsr_get_readonly_block_p, &
      31              :         dbcsr_init_p, dbcsr_p_type, dbcsr_set, dbcsr_type, dbcsr_type_antisymmetric, &
      32              :         dbcsr_type_symmetric
      33              :    USE cp_dbcsr_cp2k_link,              ONLY: cp_dbcsr_alloc_block_from_nbl
      34              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_fm,&
      35              :                                               copy_fm_to_dbcsr_bc,&
      36              :                                               dbcsr_fm_copy_plan_type
      37              :    USE cp_fm_pool_types,                ONLY: cp_fm_pool_type,&
      38              :                                               fm_pool_create_fm,&
      39              :                                               fm_pool_give_back_fm
      40              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_equivalent,&
      41              :                                               cp_fm_struct_release,&
      42              :                                               cp_fm_struct_retain,&
      43              :                                               cp_fm_struct_type
      44              :    USE cp_fm_types,                     ONLY: copy_info_type,&
      45              :                                               cp_fm_cleanup_copy_general,&
      46              :                                               cp_fm_create,&
      47              :                                               cp_fm_finish_copy_general,&
      48              :                                               cp_fm_get_info,&
      49              :                                               cp_fm_release,&
      50              :                                               cp_fm_start_copy_general,&
      51              :                                               cp_fm_type
      52              :    USE input_cp2k_kpoints,              ONLY: lattice_fft_auto,&
      53              :                                               lattice_fft_off,&
      54              :                                               lattice_fft_on
      55              :    USE kinds,                           ONLY: dp,&
      56              :                                               int_8
      57              :    USE kpoint_lattice_fft,              ONLY: cell_to_k_grid_fft,&
      58              :                                               lattice_fft_num_threads,&
      59              :                                               lattice_fft_shape,&
      60              :                                               regular_kpoint_grid
      61              :    USE kpoint_methods,                  ONLY: kp_transform_plan_create,&
      62              :                                               kp_transform_plan_type,&
      63              :                                               rskp_grid_type,&
      64              :                                               rskp_transform,&
      65              :                                               rskp_transform_grid_extract,&
      66              :                                               rskp_transform_grid_prepare,&
      67              :                                               rskp_transform_grid_release
      68              :    USE kpoint_types,                    ONLY: kpoint_env_type,&
      69              :                                               kpoint_type
      70              :    USE mathconstants,                   ONLY: gaussi,&
      71              :                                               twopi,&
      72              :                                               z_one,&
      73              :                                               z_zero
      74              :    USE message_passing,                 ONLY: mp_para_env_type
      75              :    USE qs_matrix_pools,                 ONLY: mpools_get
      76              :    USE qs_neighbor_list_types,          ONLY: neighbor_list_set_p_type
      77              : 
      78              : !$ USE omp_lib, ONLY: omp_get_max_threads, omp_get_num_threads, omp_get_thread_num
      79              : #include "./base/base_uses.f90"
      80              : 
      81              :    IMPLICIT NONE
      82              : 
      83              :    PRIVATE
      84              : 
      85              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_kpoint_operators'
      86              : 
      87              :    PUBLIC :: kpoint_operator_context_create, kpoint_operator_context_release
      88              :    PUBLIC :: kpoint_operator_start, kpoint_operator_finish
      89              :    PUBLIC :: kpoint_operator_get, kpoint_operator_prepare
      90              :    PUBLIC :: kpoint_operator_evaluate, kpoint_operator_work_size
      91              :    PUBLIC :: kpoint_operator_batch_size, kpoint_operator_evaluate_batch
      92              :    PUBLIC :: kpoint_operator_store, kpoint_operator_get_local, kpoint_operator_get_all
      93              : 
      94              :    INTEGER, PARAMETER, PUBLIC :: kpoint_slot_ks = 1, &
      95              :                                  kpoint_slot_s = 2, &
      96              :                                  kpoint_slot_t = 3
      97              : 
      98              :    !> Row of rsmat that carries the spin-free operator image. S and T carry
      99              :    !> no spin copies: drivers launch one instance per spin of their loop and
     100              :    !> read this same row in every instance.
     101              :    INTEGER, PARAMETER, PUBLIC :: kpoint_spin_free = 1
     102              : 
     103              :    !> Number of defined slots; grows only inside this module.
     104              :    INTEGER, PARAMETER, PRIVATE :: kpoint_num_slots = kpoint_slot_t - kpoint_slot_ks + 1
     105              : 
     106              :    !> Part indices of one transfer: its real and its imaginary half.
     107              :    INTEGER, PARAMETER, PRIVATE :: kpoint_part_re = 1, &
     108              :                                   kpoint_part_im = 2
     109              : 
     110              :    !> Instance states of one (k point, spin, slot) transfer.
     111              :    !> State transitions happen only inside the service routines.
     112              :    INTEGER, PARAMETER, PRIVATE :: kpoint_slot_idle = 0, &
     113              :                                   kpoint_slot_in_flight = 1, &
     114              :                                   kpoint_slot_local = 2
     115              : 
     116              :    ! Structural route shared by all captured rows. Whole blocks go to one
     117              :    ! representative group, then only equivalent tiles are replicated.
     118              :    TYPE kpoint_operator_rs_route_type
     119              :       TYPE(kp_transform_plan_type) :: traversal = kp_transform_plan_type()
     120              :       TYPE(cp_fm_struct_type), POINTER :: matrix_struct => NULL()
     121              :       INTEGER :: nimage = 0, nvalue = 0, nrow = 0, ncol = 0
     122              :       INTEGER(KIND=int_8) :: storage = 0
     123              :       INTEGER, ALLOCATABLE :: row_size(:), col_size(:), ENTRY(:, :), first(:), tile(:, :, :)
     124              :       INTEGER, ALLOCATABLE :: send_entry(:), send_first(:), scount(:, :), rcount(:, :), &
     125              :                               sdisp(:, :), rdisp(:, :)
     126              :       REAL(KIND=dp), ALLOCATABLE :: send_values(:)
     127              :       ! Geometry-only boundaries of contiguous AO-block image groups.
     128              :       INTEGER, ALLOCATABLE :: block_first(:)
     129              :    END TYPE kpoint_operator_rs_route_type
     130              : 
     131              :    ! Immutable values, not per-k matrices. Metadata belong to the shared route;
     132              :    ! source identifies starts belonging to the explicitly prepared batch only.
     133              :    TYPE kpoint_operator_block_type
     134              :       REAL(KIND=dp), POINTER :: data(:, :) => NULL()
     135              :    END TYPE kpoint_operator_block_type
     136              : 
     137              :    TYPE kpoint_operator_rs_type
     138              :       INTEGER :: matrix_row = 0
     139              :       TYPE(dbcsr_p_type), POINTER :: source(:, :) => NULL()
     140              :       ! Values are owned only for distributed sources. Blocks always borrow:
     141              :       ! either from values below or, on one world rank, directly from DBCSR.
     142              :       REAL(KIND=dp), CONTIGUOUS, POINTER :: values(:) => NULL()
     143              :       TYPE(kpoint_operator_block_type), ALLOCATABLE :: blocks(:)
     144              :       LOGICAL, ALLOCATABLE :: nonzero(:)
     145              :    END TYPE kpoint_operator_rs_type
     146              : 
     147              :    ! Read-ahead window or complete FFT output per prepared row. Keep output separate
     148              :    ! from the immutable snapshot; prepare invalidates only the selected slot.
     149              :    TYPE kpoint_operator_rs_batch_type
     150              :       INTEGER :: first = 0
     151              :       COMPLEX(KIND=dp), ALLOCATABLE :: data(:, :, :)
     152              :    END TYPE kpoint_operator_rs_batch_type
     153              : 
     154              :    INTEGER, PARAMETER :: rs_batch_size = 4
     155              :    INTEGER(KIND=int_8), PARAMETER :: rs_snapshot_bytes = 64_int_8*1024_int_8**2
     156              :    ! Includes cached tiles and batch phase scratch, divided between all rows/slots.
     157              :    INTEGER(KIND=int_8), PARAMETER :: rs_batch_bytes = 32_int_8*1024_int_8**2
     158              :    ! Shared by all prepared spin rows and slots, including transient FFT work.
     159              :    INTEGER(KIND=int_8), PARAMETER :: rs_fft_bytes = 512_int_8*1024_int_8**2
     160              : 
     161              : ! **************************************************************************************************
     162              : !> \brief Caller-owned assembly context for a fixed geometry and parallel layout.
     163              : !>        OT retains it for one inner SCF loop; other drivers use one call.
     164              : !>        Release before changing the neighbor lists, matrix pools or k-point groups.
     165              : !>        Components are private and touched only by this module.
     166              : ! **************************************************************************************************
     167              :    TYPE, PUBLIC :: kpoint_operator_context_type
     168              :       PRIVATE
     169              :       ! Instance spin capacity of the owning driver: the bound of the spin
     170              :       ! loop that start and finish key. Declared once at creation. It never
     171              :       ! comes from the operators: the first matrix dimension of a spin-free
     172              :       ! operator such as S counts derivatives, and the images live in the
     173              :       ! second, so callers address operator rows through matrix_row instead.
     174              :       INTEGER                                            :: nspin = 0
     175              :       LOGICAL                                            :: overlap_stored = .FALSE.
     176              :       ! Borrowed host/distribution references; the context cannot span an energy evaluation.
     177              :       TYPE(kpoint_type), POINTER                          :: kpoints => NULL()
     178              :       TYPE(cp_fm_struct_type), POINTER                    :: ao_ao_fmstruct => NULL()
     179              :       TYPE(cp_fm_pool_type), POINTER                      :: group_fm_pool => NULL()
     180              :       ! Identity of the neighbor lists used by scratch and prepared snapshots.
     181              :       TYPE(neighbor_list_set_p_type), DIMENSION(:), &
     182              :          POINTER                                          :: work_sab => NULL()
     183              :       ! Owned scratch, created on first use and released with the context.
     184              :       TYPE(dbcsr_type), POINTER                           :: rmatrix => NULL()
     185              :       TYPE(dbcsr_type), POINTER                           :: cmatrix => NULL()
     186              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:)         :: fmwork
     187              :       TYPE(cp_fm_type), ALLOCATABLE                       :: fmlocal, fmimag
     188              :       ! Plans share the lifetime and fixed layouts of the two densify workspaces.
     189              :       TYPE(dbcsr_fm_copy_plan_type), ALLOCATABLE          :: copy_plan(:)
     190              :       ! Immutable snapshots and bounded local output batches. Prepared starts
     191              :       ! need neither per-k transfers nor outstanding MPI requests.
     192              :       TYPE(kpoint_operator_rs_type), ALLOCATABLE          :: rs(:, :)
     193              :       TYPE(kpoint_operator_rs_route_type), ALLOCATABLE    :: rs_route
     194              :       TYPE(kpoint_operator_rs_batch_type), ALLOCATABLE    :: rs_batch(:, :)
     195              :       ! Bookkeeping. info is keyed (global k point, spin, slot, part),
     196              :       ! part kpoint_part_re holding the real half and kpoint_part_im holding
     197              :       ! the imaginary half. Status is keyed (global round, spin, slot)
     198              :       ! because start and finish treat all groups of one instance
     199              :       ! atomically. Allocated on first start.
     200              :       TYPE(copy_info_type), ALLOCATABLE, DIMENSION(:, :, :, :) :: info
     201              :       INTEGER, ALLOCATABLE, DIMENSION(:, :, :)            :: status
     202              :    END TYPE kpoint_operator_context_type
     203              : 
     204              : CONTAINS
     205              : 
     206              : ! **************************************************************************************************
     207              : !> \brief Transform and densify one operator using the context-owned workspaces and routing plans.
     208              : !> \param ctx initialized transfer context
     209              : !> \param rsmat real-space operator images
     210              : !> \param matrix_row operator row, independent of the instance spin
     211              : !> \param ik global k-point index
     212              : !> \param grid caller-owned reciprocal-grid cache
     213              : !> \param use_grid extract from grid instead of a direct phase sum
     214              : ! **************************************************************************************************
     215          512 :    SUBROUTINE kpoint_operator_densify(ctx, rsmat, matrix_row, ik, grid, use_grid)
     216              : 
     217              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     218              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
     219              :       INTEGER, INTENT(IN)                                :: matrix_row, ik
     220              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
     221              :       LOGICAL, INTENT(IN)                                :: use_grid
     222              : 
     223              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_densify'
     224              : 
     225              :       INTEGER                                            :: handle
     226          512 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: xkp
     227              : 
     228          512 :       CALL timeset(routineN, handle)
     229              : 
     230              :       ! Avoid nested component array sections in GCC 16 bounds checks.
     231          512 :       xkp => ctx%kpoints%xkp
     232          512 :       IF (ctx%kpoints%use_real_wfn) THEN
     233           16 :          IF (use_grid) THEN
     234            0 :             CPASSERT(PRESENT(grid))
     235            0 :             CALL rskp_transform_grid_extract(grid, ik, ctx%rmatrix)
     236              :          ELSE
     237           16 :             CALL dbcsr_set(ctx%rmatrix, 0.0_dp)
     238              :             CALL rskp_transform(rmatrix=ctx%rmatrix, rsmat=rsmat, ispin=matrix_row, &
     239              :                                 xkp=xkp(:, ik), cell_to_index=ctx%kpoints%cell_to_index, &
     240           16 :                                 sab_nl=ctx%kpoints%sab_nl)
     241              :          END IF
     242          496 :       ELSE IF (use_grid) THEN
     243            0 :          CPASSERT(PRESENT(grid))
     244            0 :          CALL rskp_transform_grid_extract(grid, ik, ctx%rmatrix, ctx%cmatrix)
     245              :       ELSE
     246          496 :          CALL dbcsr_set(ctx%rmatrix, 0.0_dp)
     247          496 :          CALL dbcsr_set(ctx%cmatrix, 0.0_dp)
     248              :          CALL rskp_transform(rmatrix=ctx%rmatrix, cmatrix=ctx%cmatrix, rsmat=rsmat, ispin=matrix_row, &
     249              :                              xkp=xkp(:, ik), cell_to_index=ctx%kpoints%cell_to_index, &
     250          496 :                              sab_nl=ctx%kpoints%sab_nl)
     251              :       END IF
     252          512 :       CALL copy_dbcsr_to_fm(ctx%rmatrix, ctx%fmwork(kpoint_part_re), ctx%copy_plan(kpoint_part_re))
     253          512 :       IF (.NOT. ctx%kpoints%use_real_wfn) THEN
     254          496 :          CALL copy_dbcsr_to_fm(ctx%cmatrix, ctx%fmwork(kpoint_part_im), ctx%copy_plan(kpoint_part_im))
     255              :       END IF
     256              : 
     257          512 :       CALL timestop(handle)
     258              : 
     259          512 :    END SUBROUTINE kpoint_operator_densify
     260              : 
     261              : ! **************************************************************************************************
     262              : !> \brief Launch a densified operator to its owning group (only the real part for real wavefunctions).
     263              : !>        Absent explicit targets, receive both parts through fmlocal. Each copy
     264              : !>        owns its receive buffer until finish, so the targets can alias.
     265              : !>        Non-owning groups participate with an empty dummy target.
     266              : !> \param kpoints host environment, including group ownership and transfer communicator
     267              : !> \param ik global k-point index
     268              : !> \param fmwork real/imaginary source matrices on the global distribution
     269              : !> \param fmlocal default receiver on the k-point group distribution
     270              : !> \param info transfer states, real part first; real wavefunctions allocate only the first part
     271              : !> \param target_re optional caller-owned real-part target
     272              : !> \param target_im optional imaginary-part target; required with target_re for complex wavefunctions
     273              : ! **************************************************************************************************
     274          512 :    SUBROUTINE kpoint_operator_launch(kpoints, ik, fmwork, fmlocal, info, target_re, target_im)
     275              : 
     276              :       TYPE(kpoint_type), POINTER                         :: kpoints
     277              :       INTEGER, INTENT(IN)                                :: ik
     278              :       TYPE(cp_fm_type), DIMENSION(:), INTENT(IN)         :: fmwork
     279              :       TYPE(cp_fm_type), INTENT(IN)                       :: fmlocal
     280              :       TYPE(copy_info_type), DIMENSION(:)                 :: info
     281              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: target_re, target_im
     282              : 
     283              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_launch'
     284              : 
     285              :       INTEGER                                            :: handle
     286              :       TYPE(cp_fm_type)                                   :: fmdummy
     287              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     288              : 
     289          512 :       CALL timeset(routineN, handle)
     290          512 :       CPASSERT(.NOT. PRESENT(target_im) .OR. PRESENT(target_re))
     291          512 :       IF (.NOT. kpoints%use_real_wfn) THEN
     292          496 :          CPASSERT(PRESENT(target_re) .EQV. PRESENT(target_im))
     293              :       END IF
     294              : 
     295          512 :       para_env => kpoints%blacs_env_all%para_env
     296          512 :       IF (ik >= kpoints%kp_range(1) .AND. ik <= kpoints%kp_range(2)) THEN
     297          452 :          IF (PRESENT(target_re)) THEN
     298            0 :             CALL cp_fm_start_copy_general(fmwork(kpoint_part_re), target_re, para_env, info(kpoint_part_re))
     299            0 :             IF (.NOT. kpoints%use_real_wfn) THEN
     300            0 :                CALL cp_fm_start_copy_general(fmwork(kpoint_part_im), target_im, para_env, info(kpoint_part_im))
     301              :             END IF
     302              :          ELSE
     303          452 :             CALL cp_fm_start_copy_general(fmwork(kpoint_part_re), fmlocal, para_env, info(kpoint_part_re))
     304          452 :             IF (.NOT. kpoints%use_real_wfn) THEN
     305          436 :                CALL cp_fm_start_copy_general(fmwork(kpoint_part_im), fmlocal, para_env, info(kpoint_part_im))
     306              :             END IF
     307              :          END IF
     308              :       ELSE
     309           60 :          CALL cp_fm_start_copy_general(fmwork(kpoint_part_re), fmdummy, para_env, info(kpoint_part_re))
     310           60 :          IF (.NOT. kpoints%use_real_wfn) THEN
     311           60 :             CALL cp_fm_start_copy_general(fmwork(kpoint_part_im), fmdummy, para_env, info(kpoint_part_im))
     312              :          END IF
     313              :       END IF
     314              : 
     315          512 :       CALL timestop(handle)
     316              : 
     317          512 :    END SUBROUTINE kpoint_operator_launch
     318              : 
     319              : ! **************************************************************************************************
     320              : !> \brief Receive one group's operator and reclaim every rank's send buffers.
     321              : !>        CFM/DBCSR outputs consume the context buffer before receiving the next part;
     322              : !>        FM outputs receive directly. Inactive groups only clean up their sends.
     323              : !> \param kpoints parallel environment
     324              : !> \param fmlocal shared receive buffer, consumed before receiving the next part
     325              : !> \param ik global k-point index
     326              : !> \param info real/imaginary transfers started by kpoint_operator_launch
     327              : !> \param cmat complex output, real part overwrites and imaginary part adds
     328              : !> \param fm_re real FM output
     329              : !> \param fm_im optional imaginary FM output
     330              : !> \param mat_re real DBCSR output
     331              : !> \param mat_im imaginary DBCSR output
     332              : ! **************************************************************************************************
     333          512 :    SUBROUTINE kpoint_operator_receive(kpoints, fmlocal, ik, info, cmat, fm_re, fm_im, mat_re, mat_im)
     334              :       TYPE(kpoint_type), INTENT(IN), POINTER             :: kpoints
     335              :       TYPE(cp_fm_type)                                   :: fmlocal
     336              :       INTEGER, INTENT(IN)                                :: ik
     337              :       TYPE(copy_info_type), INTENT(INOUT)                :: info(:)
     338              :       TYPE(cp_cfm_type), OPTIONAL                        :: cmat
     339              :       TYPE(cp_fm_type), OPTIONAL                         :: fm_re, fm_im
     340              :       TYPE(dbcsr_type), INTENT(INOUT), OPTIONAL          :: mat_re, mat_im
     341              : 
     342              :       INTEGER                                            :: part
     343              : 
     344          512 :       IF (ik >= kpoints%kp_range(1) .AND. ik <= kpoints%kp_range(2)) THEN
     345          452 :          IF (PRESENT(cmat)) THEN
     346          436 :             CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_re))
     347          436 :             CALL cp_cfm_scale_and_add_fm(z_zero, cmat, z_one, fmlocal)
     348          436 :             IF (.NOT. kpoints%use_real_wfn) THEN
     349          436 :                CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_im))
     350          436 :                CALL cp_cfm_scale_and_add_fm(z_one, cmat, gaussi, fmlocal)
     351              :             END IF
     352           16 :          ELSE IF (PRESENT(fm_re)) THEN
     353           16 :             CALL cp_fm_finish_copy_general(fm_re, info(kpoint_part_re))
     354           16 :             IF (kpoints%use_real_wfn) THEN
     355           16 :                IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
     356              :             ELSE
     357            0 :                CALL cp_fm_finish_copy_general(fm_im, info(kpoint_part_im))
     358              :             END IF
     359              :          ELSE
     360            0 :             CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_re))
     361            0 :             CALL copy_fm_to_dbcsr_bc(fmlocal, mat_re)
     362            0 :             IF (kpoints%use_real_wfn) THEN
     363            0 :                fmlocal%local_data(:, :) = 0.0_dp
     364              :             ELSE
     365            0 :                CALL cp_fm_finish_copy_general(fmlocal, info(kpoint_part_im))
     366              :             END IF
     367            0 :             CALL copy_fm_to_dbcsr_bc(fmlocal, mat_im)
     368              :          END IF
     369              :       END IF
     370         2016 :       DO part = 1, MERGE(1, 2, kpoints%use_real_wfn)
     371         1520 :          CALL cp_fm_cleanup_copy_general(info(part))
     372              :       END DO
     373          512 :    END SUBROUTINE kpoint_operator_receive
     374              : 
     375              : ! **************************************************************************************************
     376              : !> \brief Create an empty operator context.
     377              : !>        The context owns no buffers after this call; they are created on first
     378              : !>        use and released with the context.
     379              : !> \param ctx context to initialize
     380              : !> \param kpoints host k-point environment, referenced, not copied
     381              : !> \param ao_ao_fm any square full matrix on the environment-wide communicator;
     382              : !>        its matrix struct is the template for the context work buffers,
     383              : !>        which densify on the source side of transfers that cross into the
     384              : !>        k-point groups
     385              : !> \param nspin instance spin space of the owning driver call: the bound of
     386              : !>        the spin loop its start and finish calls iterate. Fixed for the
     387              : !>        lifetime of the context
     388              : ! **************************************************************************************************
     389        39792 :    SUBROUTINE kpoint_operator_context_create(ctx, kpoints, ao_ao_fm, nspin)
     390              : 
     391              :       TYPE(kpoint_operator_context_type), INTENT(OUT)    :: ctx
     392              :       TYPE(kpoint_type), POINTER                         :: kpoints
     393              :       TYPE(cp_fm_type), INTENT(IN)                       :: ao_ao_fm
     394              :       INTEGER, INTENT(IN)                                :: nspin
     395              : 
     396              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_context_create'
     397              : 
     398              :       INTEGER                                            :: handle
     399              : 
     400        39792 :       CALL timeset(routineN, handle)
     401              : 
     402        39792 :       CPASSERT(nspin >= 1)
     403        39792 :       ctx%nspin = nspin
     404        39792 :       CPASSERT(ASSOCIATED(kpoints))
     405        39792 :       ctx%kpoints => kpoints
     406        39792 :       CALL cp_fm_get_info(ao_ao_fm, matrix_struct=ctx%ao_ao_fmstruct)
     407        39792 :       CPASSERT(ASSOCIATED(ctx%ao_ao_fmstruct))
     408              :       ! The template must live on the transfer world. A template from
     409              :       ! another communicator fails during the transfers, far from this call.
     410        39792 :       IF (.NOT. ASSOCIATED(ctx%ao_ao_fmstruct%para_env, kpoints%blacs_env_all%para_env)) THEN
     411            0 :          CALL cp_abort(__LOCATION__, "Operator context requires the global parallel environment")
     412              :       END IF
     413              : 
     414        39792 :       CALL timestop(handle)
     415              : 
     416        39792 :    END SUBROUTINE kpoint_operator_context_create
     417              : 
     418              : ! **************************************************************************************************
     419              : !> \brief Release the context together with every buffer it created.
     420              : !>        Call once, at the end of the fixed-layout scope that created the context.
     421              : !>        Every started instance must be idle at this point. The call aborts
     422              : !>        when a transfer is still in flight, because nothing would ever wait
     423              : !>        for its MPI requests.
     424              : !> \param ctx context to release
     425              : ! **************************************************************************************************
     426        39792 :    SUBROUTINE kpoint_operator_context_release(ctx)
     427              : 
     428              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     429              : 
     430              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_context_release'
     431              : 
     432              :       INTEGER                                            :: handle, ispin, iwork, slot
     433              : 
     434        39792 :       CALL timeset(routineN, handle)
     435              : 
     436              :       ! Every started instance must have been finished: an unfinished start
     437              :       ! holds MPI requests that nothing would ever wait for.
     438        39792 :       IF (ALLOCATED(ctx%status)) THEN
     439       164190 :          CPASSERT(ALL(ctx%status == kpoint_slot_idle))
     440              :       END IF
     441              : 
     442        39792 :       IF (ASSOCIATED(ctx%rmatrix)) CALL dbcsr_deallocate_matrix(ctx%rmatrix)
     443        39792 :       IF (ASSOCIATED(ctx%cmatrix)) CALL dbcsr_deallocate_matrix(ctx%cmatrix)
     444        39792 :       NULLIFY (ctx%rmatrix, ctx%cmatrix, ctx%work_sab)
     445        40376 :       IF (ALLOCATED(ctx%copy_plan)) DEALLOCATE (ctx%copy_plan)
     446        39792 :       IF (ALLOCATED(ctx%rs)) THEN
     447       158616 :          DO slot = 1, kpoint_num_slots
     448       284724 :             DO ispin = 1, ctx%nspin
     449       245070 :                CALL kpoint_operator_rs_clear(ctx%rs(ispin, slot))
     450              :             END DO
     451              :          END DO
     452       291870 :          DEALLOCATE (ctx%rs, ctx%rs_batch)
     453              :       END IF
     454        39792 :       IF (ALLOCATED(ctx%rs_route)) THEN
     455        39642 :          CALL cp_fm_struct_release(ctx%rs_route%matrix_struct)
     456        39642 :          DEALLOCATE (ctx%rs_route)
     457              :       END IF
     458        39792 :       IF (ALLOCATED(ctx%fmwork)) THEN
     459          442 :          DO iwork = 1, SIZE(ctx%fmwork)
     460          442 :             CALL cp_fm_release(ctx%fmwork(iwork))
     461              :          END DO
     462          150 :          DEALLOCATE (ctx%fmwork)
     463              :       END IF
     464        39792 :       IF (ALLOCATED(ctx%fmimag)) THEN
     465           56 :          CALL cp_fm_release(ctx%fmimag)
     466           56 :          DEALLOCATE (ctx%fmimag)
     467              :       END IF
     468        39792 :       IF (ALLOCATED(ctx%fmlocal)) THEN
     469        39792 :          CALL fm_pool_give_back_fm(ctx%group_fm_pool, ctx%fmlocal)
     470        39792 :          DEALLOCATE (ctx%fmlocal)
     471              :       END IF
     472        39912 :       IF (ALLOCATED(ctx%info)) DEALLOCATE (ctx%info)
     473        39792 :       IF (ALLOCATED(ctx%status)) DEALLOCATE (ctx%status)
     474        39792 :       NULLIFY (ctx%kpoints, ctx%ao_ao_fmstruct, ctx%group_fm_pool)
     475        39792 :       ctx%overlap_stored = .FALSE.
     476              : 
     477        39792 :       CALL timestop(handle)
     478              : 
     479        39792 :    END SUBROUTINE kpoint_operator_context_release
     480              : 
     481              : ! **************************************************************************************************
     482              : !> \brief Create the context scratch that the first service call needs: the
     483              : !>        DBCSR work pair with blocks from the current neighbor lists, two
     484              : !>        densify buffers on the environment-wide struct, and the group
     485              : !>        transfer target from the group pools.
     486              : !>        The per-instance slot bookkeeping is not scratch: start creates it
     487              : !>        on its first call, because the synchronous get call never touches it.
     488              : !>        The buffers assume a fixed-layout context scope: the
     489              : !>        host neighbor lists do not change within it, and every buffer use
     490              : !>        asserts this identity. Buffers from an earlier call stay in place.
     491              : !>        Called from the service routines only.
     492              : !> \param ctx context that receives the buffers
     493              : !> \param rsmat real-space image matrices of one operator. The call uses the
     494              : !>        first matrix as the DBCSR template of the work pair. It reads no
     495              : !>        matrix data.
     496              : ! **************************************************************************************************
     497          452 :    SUBROUTINE kpoint_operator_ensure(ctx, rsmat)
     498              : 
     499              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     500              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
     501              : 
     502              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_ensure'
     503              : 
     504              :       INTEGER                                            :: handle
     505              : 
     506          452 :       CALL timeset(routineN, handle)
     507              : 
     508              :       ! The pair must still match the host neighbor lists: they are
     509              :       ! reallocated at every energy evaluation; the context stays within it.
     510          452 :       IF (ASSOCIATED(ctx%work_sab)) THEN
     511          302 :          CPASSERT(ASSOCIATED(ctx%work_sab, ctx%kpoints%sab_nl))
     512              :       END IF
     513          452 :       IF (.NOT. ASSOCIATED(ctx%rmatrix)) THEN
     514          150 :          ALLOCATE (ctx%rmatrix)
     515          150 :          CALL dbcsr_create(ctx%rmatrix, template=rsmat(1, 1)%matrix, matrix_type=dbcsr_type_symmetric)
     516          150 :          CALL cp_dbcsr_alloc_block_from_nbl(ctx%rmatrix, ctx%kpoints%sab_nl)
     517          150 :          IF (.NOT. ctx%kpoints%use_real_wfn) THEN
     518          142 :             ALLOCATE (ctx%cmatrix)
     519          142 :             CALL dbcsr_create(ctx%cmatrix, template=rsmat(1, 1)%matrix, matrix_type=dbcsr_type_antisymmetric)
     520          142 :             CALL cp_dbcsr_alloc_block_from_nbl(ctx%cmatrix, ctx%kpoints%sab_nl)
     521              :          END IF
     522          150 :          ctx%work_sab => ctx%kpoints%sab_nl
     523              :       END IF
     524              : 
     525          452 :       IF (.NOT. ALLOCATED(ctx%fmwork)) THEN
     526          734 :          ALLOCATE (ctx%fmwork(MERGE(1, 2, ctx%kpoints%use_real_wfn)))
     527          742 :          ALLOCATE (ctx%copy_plan(SIZE(ctx%fmwork)))
     528              :          CALL cp_fm_create(ctx%fmwork(kpoint_part_re), ctx%ao_ao_fmstruct, &
     529          150 :                            name="KPOINT-OPERATOR-WORK-RE")
     530          150 :          IF (.NOT. ctx%kpoints%use_real_wfn) THEN
     531              :             CALL cp_fm_create(ctx%fmwork(kpoint_part_im), ctx%ao_ao_fmstruct, &
     532          142 :                               name="KPOINT-OPERATOR-WORK-IM")
     533              :          END IF
     534              :       END IF
     535              : 
     536          452 :       CALL kpoint_operator_ensure_group(ctx)
     537              : 
     538          452 :       CALL timestop(handle)
     539              : 
     540          452 :    END SUBROUTINE kpoint_operator_ensure
     541              : 
     542              : ! **************************************************************************************************
     543              : !> \brief Acquire only the group-local output workspace; both backends share it.
     544              : !> \param ctx operator context
     545              : ! **************************************************************************************************
     546        48632 :    SUBROUTINE kpoint_operator_ensure_group(ctx)
     547              : 
     548              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     549              : 
     550        48632 :       IF (.NOT. ALLOCATED(ctx%fmlocal)) THEN
     551        39792 :          CALL mpools_get(ctx%kpoints%mpools, ao_ao_fm_pool=ctx%group_fm_pool)
     552        39792 :          ALLOCATE (ctx%fmlocal)
     553        39792 :          CALL fm_pool_create_fm(ctx%group_fm_pool, ctx%fmlocal)
     554              :       END IF
     555              : 
     556        48632 :    END SUBROUTINE kpoint_operator_ensure_group
     557              : 
     558              : ! **************************************************************************************************
     559              : !> \brief Prepare an immutable operator slot for group-local assembly.
     560              : !>        Refresh after the source values change, with every instance idle.
     561              : !>        Other prepared slots and structural routing remain valid. Without
     562              : !>        matrix_row, capture one row per instance spin; with matrix_row, store
     563              : !>        one row shared by all instance spins. Failure clears this slot on
     564              : !>        all ranks and retains the existing transfer fallback.
     565              : !> \param ctx context spanning the current initialized k-point environment
     566              : !> \param rsmat current real-space operator images
     567              : !> \param slot slot to replace (KS, S or T)
     568              : !> \param prepared optional collective success flag
     569              : !> \param matrix_row optional explicit spin-free operator row
     570              : !> \param TARGET optional group-local FM layout for prepared get outputs;
     571              : !>        otherwise use the host AO/AO pool layout. One layout per context
     572              : ! **************************************************************************************************
     573        46784 :    SUBROUTINE kpoint_operator_prepare(ctx, rsmat, slot, prepared, matrix_row, TARGET)
     574              : 
     575              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
     576              :       TYPE(dbcsr_p_type), POINTER                        :: rsmat(:, :)
     577              :       INTEGER, INTENT(IN)                                :: slot
     578              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: prepared
     579              :       INTEGER, INTENT(IN), OPTIONAL                      :: matrix_row
     580              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: target
     581              : 
     582              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_prepare'
     583              : 
     584              :       INTEGER                                            :: compatible, handle, ispin, jslot, nrow, &
     585              :                                                             row
     586              :       INTEGER(KIND=int_8)                                :: retained, storage
     587              :       LOGICAL                                            :: ready
     588              :       TYPE(cp_fm_struct_type), POINTER                   :: fms
     589              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     590              : 
     591        46784 :       CALL timeset(routineN, handle)
     592        46784 :       IF (PRESENT(prepared)) prepared = .FALSE.
     593        46784 :       CPASSERT(ASSOCIATED(ctx%kpoints))
     594        46784 :       CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
     595        46784 :       CPASSERT(ASSOCIATED(ctx%kpoints%sab_nl))
     596        46784 :       IF (ASSOCIATED(ctx%work_sab)) THEN
     597         7120 :          CPASSERT(ASSOCIATED(ctx%work_sab, ctx%kpoints%sab_nl))
     598              :       END IF
     599        46784 :       IF (ALLOCATED(ctx%status)) THEN
     600            0 :          CPASSERT(ALL(ctx%status == kpoint_slot_idle))
     601              :       END IF
     602        46784 :       IF (.NOT. ALLOCATED(ctx%rs)) THEN
     603       688410 :          ALLOCATE (ctx%rs(ctx%nspin, kpoint_num_slots), ctx%rs_batch(ctx%nspin, kpoint_num_slots))
     604              :       END IF
     605        96858 :       DO ispin = 1, ctx%nspin
     606        50074 :          CALL kpoint_operator_rs_clear(ctx%rs(ispin, slot))
     607        50074 :          IF (ALLOCATED(ctx%rs_batch(ispin, slot)%data)) DEALLOCATE (ctx%rs_batch(ispin, slot)%data)
     608        96858 :          ctx%rs_batch(ispin, slot)%first = 0
     609              :       END DO
     610        46784 :       nrow = ctx%nspin
     611        46784 :       IF (PRESENT(matrix_row)) THEN
     612         7608 :          CPASSERT(matrix_row >= 1 .AND. matrix_row <= SIZE(rsmat, 1))
     613              :          nrow = 1
     614              :       ELSE
     615        39176 :          CPASSERT(SIZE(rsmat, 1) == ctx%nspin)
     616              :       END IF
     617        46784 :       para_env => ctx%kpoints%blacs_env_all%para_env
     618        46784 :       CALL kpoint_operator_ensure_group(ctx)
     619        46784 :       fms => ctx%fmlocal%matrix_struct
     620        46784 :       IF (PRESENT(TARGET)) fms => target%matrix_struct
     621        46784 :       CPASSERT(ASSOCIATED(fms))
     622              :       ! Routing uses the host group-rank numbering. Different BLACS layouts
     623              :       ! on that communicator are supported, foreign communicators are not.
     624        46784 :       compatible = MERGE(1, 0, ASSOCIATED(fms%para_env, ctx%kpoints%para_env_kp))
     625        46784 :       CALL para_env%min(compatible)
     626        46784 :       IF (compatible == 0) THEN
     627            0 :          CALL timestop(handle)
     628           22 :          RETURN
     629              :       END IF
     630        46784 :       IF (.NOT. ALLOCATED(ctx%rs_route)) THEN
     631        39664 :          ALLOCATE (ctx%rs_route)
     632        39664 :          CALL kpoint_operator_rs_plan_create(ctx%kpoints, rsmat, fms, rs_snapshot_bytes, ctx%rs_route, ready)
     633        39664 :          IF (.NOT. ready) THEN
     634           22 :             DEALLOCATE (ctx%rs_route)
     635           22 :             CALL timestop(handle)
     636           22 :             RETURN
     637              :          END IF
     638        39642 :          ctx%work_sab => ctx%kpoints%sab_nl
     639              :       END IF
     640        46762 :       compatible = MERGE(1, 0, cp_fm_struct_equivalent(fms, ctx%rs_route%matrix_struct))
     641        96222 :       DO ispin = 1, nrow
     642        49460 :          row = ispin
     643        49460 :          IF (PRESENT(matrix_row)) row = matrix_row
     644        96222 :          IF (.NOT. kpoint_operator_rs_compatible(ctx%rs_route, rsmat, row)) compatible = 0
     645              :       END DO
     646        46762 :       CALL para_env%min(compatible)
     647              :       ! Keep the single-k phase allowance in the snapshot budget for the uncached fallback.
     648        46762 :       retained = 0_int_8
     649       187048 :       DO jslot = kpoint_slot_ks, kpoint_slot_t
     650       337204 :          DO ispin = 1, ctx%nspin
     651       150156 :             IF (.NOT. ALLOCATED(ctx%rs(ispin, jslot)%blocks)) CYCLE
     652         6024 :             retained = retained + 128_int_8*SIZE(ctx%rs(ispin, jslot)%blocks, KIND=int_8)
     653       146310 :             IF (ASSOCIATED(ctx%rs(ispin, jslot)%values)) THEN
     654         6024 :                retained = retained + 8_int_8*SIZE(ctx%rs(ispin, jslot)%values, KIND=int_8)
     655              :             END IF
     656              :          END DO
     657              :       END DO
     658              :       storage = ctx%rs_route%storage + retained + INT(nrow, int_8)* &
     659              :                 (MERGE(0_int_8, 8_int_8*ctx%rs_route%nvalue, para_env%num_pe == 1) + &
     660        46762 :                  128_int_8*SIZE(ctx%rs_route%entry, 2))
     661        46762 :       CALL para_env%max(storage)
     662        46762 :       ready = compatible == 1 .AND. storage <= rs_snapshot_bytes
     663        46762 :       IF (ready) THEN
     664        96222 :          DO ispin = 1, nrow
     665        49460 :             row = ispin
     666        49460 :             IF (PRESENT(matrix_row)) row = matrix_row
     667        49460 :             CALL kpoint_operator_rs_capture(rsmat, row, ctx%rs_route, ctx%kpoints, ctx%rs(ispin, slot))
     668              :             CALL kpoint_operator_fft_prepare(ctx%rs(ispin, slot), ctx%rs_route, ctx%kpoints, &
     669        96222 :                                              ctx%rs_batch(ispin, slot), rs_fft_bytes/INT(ctx%nspin*kpoint_num_slots, int_8))
     670              :          END DO
     671              :       END IF
     672        46762 :       IF (PRESENT(prepared)) prepared = ready
     673        46762 :       CALL timestop(handle)
     674              : 
     675              :    END SUBROUTINE kpoint_operator_prepare
     676              : 
     677              : ! **************************************************************************************************
     678              : !> \brief Release one captured row without invalidating other slots or routing.
     679              : !> \param snapshot row to clear
     680              : ! **************************************************************************************************
     681       178590 :    SUBROUTINE kpoint_operator_rs_clear(snapshot)
     682              : 
     683              :       TYPE(kpoint_operator_rs_type), INTENT(INOUT)       :: snapshot
     684              : 
     685       178590 :       IF (ALLOCATED(snapshot%blocks)) DEALLOCATE (snapshot%blocks, snapshot%nonzero)
     686       178590 :       IF (ASSOCIATED(snapshot%values)) DEALLOCATE (snapshot%values)
     687       178590 :       NULLIFY (snapshot%source)
     688       178590 :       snapshot%matrix_row = 0
     689              : 
     690       178590 :    END SUBROUTINE kpoint_operator_rs_clear
     691              : 
     692              : ! **************************************************************************************************
     693              : !> \brief Transform disjoint AO entries, then exchange only group-owned k-point results.
     694              : !>        The snapshot stays usable for direct assembly if any rank rejects the FFT.
     695              : !>        All pool/planner calls and communication precede OpenMP k-point workers.
     696              : !> \param snapshot captured real-space blocks
     697              : !> \param route prepared group-local layout
     698              : !> \param kpoints host sampling and communicators
     699              : !> \param work existing matrix-batch cache, filled for all group-owned k points
     700              : !> \param max_bytes budget for this row's output, maps and transient FFT/exchange buffers
     701              : ! **************************************************************************************************
     702            0 :    SUBROUTINE kpoint_operator_fft_prepare(snapshot, route, kpoints, work, max_bytes)
     703              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
     704              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
     705              :       TYPE(kpoint_type), POINTER                         :: kpoints
     706              :       TYPE(kpoint_operator_rs_batch_type), INTENT(OUT)   :: work
     707              :       INTEGER(KIND=int_8), INTENT(IN)                    :: max_bytes
     708              : 
     709        49460 :       COMPLEX(KIND=dp), ALLOCATABLE, TARGET              :: recvbuf(:), sendbuf(:)
     710        49460 :       COMPLEX(KIND=dp), CONTIGUOUS, POINTER              :: values_k(:, :, :)
     711              :       INTEGER                                            :: batch, count, first, group, handle, i, &
     712              :                                                             ik, j, last, local_k, mine, ncell, &
     713              :                                                             nel, ngroup, nkp, offset, pos, width
     714              :       INTEGER(KIND=int_8)                                :: fixed_bytes, per_entry, tile_size
     715        49460 :       INTEGER, ALLOCATABLE                               :: cells(:, :), ranges(:, :), rcount(:), &
     716        49460 :                                                             rdispl(:), scount(:), sdispl(:)
     717              :       INTEGER, DIMENSION(12)                             :: layout, layout_max, layout_min
     718              :       INTEGER, DIMENSION(2)                              :: choice
     719              :       INTEGER, DIMENSION(3)                              :: nfft
     720        49460 :       REAL(KIND=dp), ALLOCATABLE                         :: values_rs(:, :, :)
     721              :       TYPE(cp_fm_struct_type), POINTER                   :: fms
     722              :       TYPE(mp_para_env_type), POINTER                    :: inter, world
     723              : 
     724        49460 :       IF (kpoints%lattice_fft == lattice_fft_off) RETURN
     725        49460 :       nkp = SIZE(kpoints%xkp, 2)
     726        49460 :       IF (kpoints%lattice_fft == lattice_fft_auto .AND. nkp < 27) RETURN
     727         1316 :       IF (ANY(kpoints%nkp_grid <= 0)) RETURN
     728         1304 :       IF (32_int_8*PRODUCT(INT(kpoints%nkp_grid, int_8)) > max_bytes) RETURN
     729          326 :       IF (.NOT. lattice_fft_shape(kpoints%nkp_grid, nfft, allow_arbitrary=.TRUE.)) RETURN
     730         1304 :       IF (32_int_8*PRODUCT(INT(nfft, int_8)) > max_bytes) RETURN
     731          326 :       IF (.NOT. regular_kpoint_grid(kpoints%xkp, kpoints%nkp_grid, allow_incomplete=.TRUE.)) RETURN
     732              : 
     733          326 :       world => kpoints%blacs_env_all%para_env
     734          326 :       inter => kpoints%para_env_inter_kp
     735          326 :       ngroup = inter%num_pe
     736          326 :       mine = inter%mepos + 1
     737          326 :       local_k = kpoints%kp_range(2) - kpoints%kp_range(1) + 1
     738          326 :       fms => route%matrix_struct
     739              :       ! Equivalent inter-group ranks must agree on both local indices and global layout.
     740              :       layout = [fms%nrow_global, fms%ncol_global, fms%nrow_block, fms%ncol_block, &
     741         6194 :                 fms%first_p_pos, fms%context%num_pe, fms%context%mepos, route%nrow, route%ncol]
     742          326 :       layout_min = layout
     743          326 :       layout_max = layout
     744          326 :       CALL inter%min(layout_min)
     745          326 :       CALL inter%max(layout_max)
     746          326 :       tile_size = INT(route%nrow, int_8)*route%ncol
     747          326 :       nel = 0
     748          326 :       IF (tile_size <= HUGE(nel)) nel = INT(tile_size)
     749              :       fixed_bytes = 16_int_8*tile_size*local_k + &
     750              :                     48_int_8*PRODUCT(INT(nfft, int_8))*lattice_fft_num_threads(nfft, nel) + &
     751              :                     4_int_8*PRODUCT(INT(kpoints%nkp_grid, int_8)) + 12_int_8*nkp + &
     752         2282 :                     80_int_8*SIZE(route%entry, 2, KIND=int_8) + 24_int_8*ngroup
     753              :       per_entry = 16_int_8*SIZE(route%entry, 2, KIND=int_8) + &
     754          326 :                   16_int_8*(INT(nkp, int_8) + INT(ngroup, int_8)*local_k)
     755              :       width = INT(MAX(0_int_8, MIN(INT((nel - 1)/ngroup + 1, int_8), &
     756          326 :                                    (max_bytes - fixed_bytes)/MAX(1_int_8, per_entry))))
     757         4890 :       choice = [MERGE(1, 0, nel > 0 .AND. ALL(layout_min == layout_max)), width]
     758          326 :       CALL world%min(choice)
     759          326 :       IF (choice(1) == 0 .OR. choice(2) == 0) RETURN
     760          326 :       width = choice(2)
     761          326 :       IF (kpoints%lattice_fft == lattice_fft_auto .AND. ngroup > 1) THEN
     762          468 :          IF (16_int_8*width*MINVAL(kpoints%kp_dist(2, :) - kpoints%kp_dist(1, :) + 1) < 32_int_8*1024) RETURN
     763              :       END IF
     764              : 
     765          190 :       CALL timeset("kpoint_operator_fft_prepare", handle)
     766          190 :       ncell = 2*SIZE(route%entry, 2)
     767          950 :       ALLOCATE (cells(3, ncell), ranges(2, ngroup))
     768       211088 :       DO i = 1, SIZE(route%entry, 2)
     769       843592 :          cells(:, 2*i - 1) = route%entry(5:7, i)
     770       843782 :          cells(:, 2*i) = -route%entry(5:7, i)
     771              :       END DO
     772          404 :       DO group = 1, ngroup
     773          214 :          ranges(1, group) = INT(INT(nel, int_8)*(group - 1)/ngroup) + 1
     774          404 :          ranges(2, group) = INT(INT(nel, int_8)*group/ngroup)
     775              :       END DO
     776          950 :       ALLOCATE (work%data(local_k, route%nrow, route%ncol))
     777         1520 :       ALLOCATE (values_rs(width, 1, ncell), sendbuf(width*nkp), recvbuf(width*ngroup*local_k))
     778         1140 :       ALLOCATE (scount(ngroup), sdispl(ngroup), rcount(ngroup), rdispl(ngroup))
     779          671 :       DO batch = 1, MAXVAL(ranges(2, :) - ranges(1, :) + 1), width
     780          267 :          first = ranges(1, mine) + batch - 1
     781          267 :          last = MIN(first + width - 1, ranges(2, mine))
     782          267 :          count = MAX(0, last - first + 1)
     783          267 :          IF (count > 0) THEN
     784          267 :             CALL kpoint_operator_fft_pack(snapshot, route, first, last, values_rs(1:count, :, :))
     785          267 :             values_k(1:count, 1:1, 1:nkp) => sendbuf(1:count*nkp)
     786              :             ! The transform itself supplies a direct result if the FFT backend fails.
     787              :             CALL cell_to_k_grid_fft(values_rs(1:count, :, :), cells, kpoints%xkp, kpoints%nkp_grid, &
     788          267 :                                     values_k)
     789              :          END IF
     790              :          offset = 0
     791          558 :          DO group = 1, ngroup
     792          291 :             scount(group) = count*(kpoints%kp_dist(2, group) - kpoints%kp_dist(1, group) + 1)
     793          291 :             sdispl(group) = count*(kpoints%kp_dist(1, group) - 1)
     794          291 :             first = ranges(1, group) + batch - 1
     795          291 :             last = MIN(first + width - 1, ranges(2, group))
     796          291 :             rcount(group) = MAX(0, last - first + 1)*local_k
     797          291 :             rdispl(group) = offset
     798          558 :             offset = offset + rcount(group)
     799              :          END DO
     800          267 :          CALL inter%alltoall(sendbuf, scount, sdispl, recvbuf, rcount, rdispl)
     801          748 :          DO group = 1, ngroup
     802          291 :             first = ranges(1, group) + batch - 1
     803          291 :             last = MIN(first + width - 1, ranges(2, group))
     804              : !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) &
     805          558 : !$OMP SHARED(work,route,recvbuf,rdispl,group,first,last,local_k) PRIVATE(ik,i,j,pos)
     806              :             DO pos = first, last
     807              :                i = MODULO(pos - 1, route%nrow) + 1
     808              :                j = (pos - 1)/route%nrow + 1
     809              :                DO ik = 1, local_k
     810              :                   work%data(ik, i, j) = recvbuf(rdispl(group) + (ik - 1)*(last - first + 1) + pos - first + 1)
     811              :                END DO
     812              :             END DO
     813              : !$OMP END PARALLEL DO
     814              :          END DO
     815              :       END DO
     816          190 :       work%first = kpoints%kp_range(1)
     817          190 :       CALL timestop(handle)
     818        99110 :    END SUBROUTINE kpoint_operator_fft_prepare
     819              : 
     820              : ! **************************************************************************************************
     821              : !> \brief Pack independent AO entries with the same mirror/sign convention as direct assembly.
     822              : !> \param snapshot captured real-space blocks
     823              : !> \param route prepared group-local layout
     824              : !> \param first first flattened local AO entry
     825              : !> \param last last flattened local AO entry
     826              : !> \param values real-cell batch; odd/even cells hold a block and its Hermitian mirror
     827              : ! **************************************************************************************************
     828          267 :    SUBROUTINE kpoint_operator_fft_pack(snapshot, route, first, last, values)
     829              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
     830              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
     831              :       INTEGER, INTENT(IN)                                :: first, last
     832              :       REAL(KIND=dp), INTENT(OUT)                         :: values(:, :, :)
     833              : 
     834              :       INTEGER                                            :: col, element, iblock, part, row, &
     835              :                                                             source_col, source_row
     836              : 
     837    861965903 :       values(:, :, :) = 0.0_dp
     838              : !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) &
     839          267 : !$OMP SHARED(snapshot,route,first,last,values) PRIVATE(element,row,col,iblock,part,source_row,source_col)
     840              :       DO element = first, last
     841              :          row = MODULO(element - 1, route%nrow) + 1
     842              :          col = (element - 1)/route%nrow + 1
     843              :          DO iblock = 1, SIZE(route%entry, 2)
     844              :             IF (.NOT. snapshot%nonzero(iblock)) CYCLE
     845              :             DO part = 1, 2
     846              :                IF (part == 2 .AND. route%entry(8, iblock) == 0) CYCLE
     847              :                IF (row < route%tile(1, part, iblock) .OR. row > route%tile(2, part, iblock)) CYCLE
     848              :                IF (col < route%tile(3, part, iblock) .OR. col > route%tile(4, part, iblock)) CYCLE
     849              :                source_row = route%matrix_struct%row_indices(row)
     850              :                source_col = route%matrix_struct%col_indices(col)
     851              :                IF (part == 2) THEN
     852              :                   source_col = route%matrix_struct%row_indices(row)
     853              :                   source_row = route%matrix_struct%col_indices(col)
     854              :                END IF
     855              :                values(element - first + 1, 1, 2*iblock - 2 + part) = &
     856              :                   snapshot%blocks(iblock)%data(source_row - route%entry(1, iblock) + 1, &
     857              :                                                source_col - route%entry(2, iblock) + 1)
     858              :             END DO
     859              :          END DO
     860              :       END DO
     861              : !$OMP END PARALLEL DO
     862          267 :    END SUBROUTINE kpoint_operator_fft_pack
     863              : 
     864              : ! **************************************************************************************************
     865              : !> \brief Copy a cached matrix into a possibly padded solver matrix, without MPI.
     866              : !> \param values cached reciprocal matrix
     867              : !> \param TARGET complex output
     868              : !> \param fm_re real output
     869              : !> \param fm_im optional imaginary output
     870              : ! **************************************************************************************************
     871        24327 :    SUBROUTINE kpoint_operator_rs_copy(values, TARGET, fm_re, fm_im)
     872              :       COMPLEX(KIND=dp), INTENT(IN)                       :: values(:, :)
     873              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
     874              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
     875              : 
     876              :       INTEGER                                            :: ncol, nrow
     877              : 
     878        24327 :       nrow = SIZE(values, 1)
     879        24327 :       ncol = SIZE(values, 2)
     880        24327 :       CPASSERT(PRESENT(TARGET) .NEQV. PRESENT(fm_re))
     881        24327 :       IF (PRESENT(TARGET)) THEN
     882     29862737 :          target%local_data(1:nrow, 1:ncol) = values
     883       725250 :          target%local_data(nrow + 1:, :) = z_zero
     884        23539 :          target%local_data(:, ncol + 1:) = z_zero
     885              :       ELSE
     886        89338 :          fm_re%local_data(1:nrow, 1:ncol) = REAL(values, dp)
     887         8200 :          fm_re%local_data(nrow + 1:, :) = 0.0_dp
     888          788 :          fm_re%local_data(:, ncol + 1:) = 0.0_dp
     889          788 :          IF (PRESENT(fm_im)) THEN
     890        89338 :             fm_im%local_data(1:nrow, 1:ncol) = AIMAG(values)
     891         8200 :             fm_im%local_data(nrow + 1:, :) = 0.0_dp
     892          788 :             fm_im%local_data(:, ncol + 1:) = 0.0_dp
     893              :          END IF
     894              :       END IF
     895        24327 :    END SUBROUTINE kpoint_operator_rs_copy
     896              : 
     897              : ! **************************************************************************************************
     898              : !> \brief Match an explicitly prepared row, sharing spin-free rows across spins.
     899              : !> \param ctx operator context
     900              : !> \param source live array identity identifying the prepared operation
     901              : !> \param ispin instance spin
     902              : !> \param slot prepared slot
     903              : !> \param matrix_row operator row
     904              : !> \return snapshot spin index, or zero for the transfer backend
     905              : ! **************************************************************************************************
     906        35804 :    FUNCTION kpoint_operator_rs_match(ctx, source, ispin, slot, matrix_row) RESULT(irs)
     907              : 
     908              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
     909              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     910              :       INTEGER, INTENT(IN)                                :: ispin, slot, matrix_row
     911              :       INTEGER                                            :: irs
     912              : 
     913              :       INTEGER                                            :: i
     914              : 
     915        35804 :       irs = 0
     916        35804 :       IF (.NOT. ALLOCATED(ctx%rs)) RETURN
     917        35804 :       i = ispin
     918        35804 :       IF (.NOT. ASSOCIATED(ctx%rs(i, slot)%source)) i = 1
     919        35804 :       IF (ASSOCIATED(ctx%rs(i, slot)%source, source) .AND. ctx%rs(i, slot)%matrix_row == matrix_row) irs = i
     920              : 
     921              :    END FUNCTION kpoint_operator_rs_match
     922              : 
     923              : ! **************************************************************************************************
     924              : !> \brief Cache whole-block routing to the FM tiles of one representative group.
     925              : !>        Equivalent ranks in the other groups receive only their tile's blocks.
     926              : !>        Boundary blocks may overlap several tiles. Missing blocks are packed
     927              : !>        as zero, permitting different operator sparsities on a fixed AO layout.
     928              : !> \param kpoints initialized parallel environment and neighbor lists
     929              : !> \param source operator layout template
     930              : !> \param matrix_struct target group layout
     931              : !> \param max_bytes per-rank budget for routing and capture buffers
     932              : !> \param route context-owned structural route
     933              : !> \param ready false collectively if unsupported or over budget
     934              : ! **************************************************************************************************
     935        39664 :    SUBROUTINE kpoint_operator_rs_plan_create(kpoints, source, matrix_struct, max_bytes, route, ready)
     936              : 
     937              :       TYPE(kpoint_type), POINTER                         :: kpoints
     938              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
     939              :       TYPE(cp_fm_struct_type), POINTER                   :: matrix_struct
     940              :       INTEGER(KIND=int_8), INTENT(IN)                    :: max_bytes
     941              :       TYPE(kpoint_operator_rs_route_type), INTENT(OUT)   :: route
     942              :       LOGICAL, INTENT(OUT)                               :: ready
     943              : 
     944              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_rs_plan_create'
     945              : 
     946              :       INTEGER                                            :: compatible, dest, handle, i, iblock, &
     947              :                                                             igroup, nblock, ncol, ngroup, nrow, p, &
     948              :                                                             pos, rank
     949              :       INTEGER(KIND=int_8)                                :: nr(2), ns(2), storage
     950        39664 :       INTEGER(KIND=int_8), ALLOCATABLE                   :: recv_size(:, :), send_size(:, :)
     951        39664 :       INTEGER, ALLOCATABLE                               :: identities(:, :), owner(:), &
     952        39664 :                                                             position(:, :), send_metadata(:, :)
     953        39664 :       INTEGER, POINTER                                   :: col_size(:), row_size(:)
     954        39664 :       LOGICAL, ALLOCATABLE                               :: needed(:)
     955              :       TYPE(mp_para_env_type), POINTER                    :: inter, world
     956              : 
     957        39664 :       CALL timeset(routineN, handle)
     958        39664 :       ready = .FALSE.
     959        39664 :       world => kpoints%blacs_env_all%para_env
     960        39664 :       inter => kpoints%para_env_inter_kp
     961        39664 :       CALL dbcsr_get_info(source(1, 1)%matrix, row_blk_size=row_size, col_blk_size=col_size)
     962              :       CALL kp_transform_plan_create(route%traversal, kpoints%sab_nl, kpoints%cell_to_index, &
     963        39664 :                                     SIZE(source, 2), block_template=source(1, 1)%matrix)
     964        39664 :       compatible = MERGE(1, 0, route%traversal%symmetric)
     965       366668 :       IF (SUM(row_size) /= matrix_struct%nrow_global .OR. &
     966            0 :           SUM(col_size) /= matrix_struct%ncol_global) compatible = 0
     967        39664 :       IF (SIZE(row_size) == SIZE(col_size)) THEN
     968       203166 :          IF (ANY(row_size /= col_size)) compatible = 0
     969              :       ELSE
     970            0 :          compatible = 0
     971              :       END IF
     972        39664 :       CALL world%min(compatible)
     973        39664 :       IF (compatible == 0) THEN
     974            0 :          CALL timestop(handle)
     975            0 :          RETURN
     976              :       END IF
     977        39664 :       route%nimage = SIZE(source, 2)
     978       198320 :       ALLOCATE (route%row_size(SIZE(row_size)), route%col_size(SIZE(col_size)))
     979       203166 :       route%row_size(:) = row_size
     980       203166 :       route%col_size(:) = col_size
     981              :       ALLOCATE (identities(2, 0:world%num_pe - 1), owner(0:kpoints%para_env_kp%num_pe - 1), &
     982       237984 :                 needed(0:kpoints%para_env_kp%num_pe - 1))
     983       118992 :       CALL world%allgather([kpoints%para_env_kp%mepos, inter%mepos], identities)
     984        91062 :       owner(:) = -1
     985       118992 :       DO rank = 0, world%num_pe - 1
     986       118992 :          IF (identities(2, rank) == 0) owner(identities(1, rank)) = rank
     987              :       END DO
     988        91062 :       CPASSERT(ALL(owner >= 0))
     989       158656 :       ALLOCATE (send_size(2, 0:world%num_pe - 1), recv_size(2, 0:world%num_pe - 1))
     990        39664 :       send_size(:, :) = 0_int_8
     991      8505757 :       DO i = 1, route%traversal%nentry
     992      8466093 :          nrow = row_size(route%traversal%row(i))
     993      8466093 :          ncol = col_size(route%traversal%col(i))
     994      8466093 :          CALL kpoint_operator_rs_owners(route, matrix_struct, i, needed)
     995     20930721 :          DO p = 0, SIZE(owner) - 1
     996     12424964 :             IF (.NOT. needed(p)) CYCLE
     997     10316826 :             dest = owner(p)
     998     10316826 :             send_size(1, dest) = send_size(1, dest) + 1_int_8
     999     20891057 :             send_size(2, dest) = send_size(2, dest) + INT(nrow, int_8)*ncol
    1000              :          END DO
    1001              :       END DO
    1002        39664 :       CALL world%alltoall(send_size, recv_size, 2)
    1003              :       ! Only inter-rank zero receives from the world. The other groups hold
    1004              :       ! equivalent FM tiles, as required by kpoint_env_initialize.
    1005       277648 :       ns = SUM(send_size, DIM=2)
    1006       277648 :       nr = SUM(recv_size, DIM=2)
    1007        39664 :       CALL inter%bcast(nr, 0)
    1008              :       ! Conservative peak: traversal, retained route, temporary metadata and
    1009              :       ! one snapshot. The full batch budget is checked again by prepare.
    1010              :       storage = 40_int_8*route%traversal%nentry + &
    1011              :                 4_int_8*(SIZE(row_size) + SIZE(col_size) + matrix_struct%nrow_global + matrix_struct%ncol_global) + &
    1012              :                 48_int_8*ns(1) + MERGE(0_int_8, 8_int_8*ns(2), world%num_pe == 1) + 68_int_8*nr(1) + &
    1013        39664 :                 128_int_8*world%num_pe + 8_int_8*SIZE(owner) + 8_int_8
    1014              :       ! Singleton groups also retain the AO-block group boundaries.
    1015        39664 :       IF (SIZE(owner) == 1) storage = storage + 4_int_8*(nr(1) + 1_int_8)
    1016        39664 :       route%storage = storage
    1017        79328 :       storage = storage + MERGE(0_int_8, 8_int_8*nr(2), world%num_pe == 1) + 128_int_8*nr(1)
    1018              :       ! Borrowed values do not consume the snapshot budget; still bound integer route offsets.
    1019       198320 :       IF (MAXVAL(ns) >= INT(HUGE(0), int_8) .OR. MAXVAL(nr) >= INT(HUGE(0), int_8)) THEN
    1020            0 :          storage = MAX(storage, max_bytes + 1_int_8)
    1021              :       END IF
    1022        39664 :       CALL world%max(storage)
    1023        39664 :       IF (storage > max_bytes) THEN
    1024           22 :          CALL timestop(handle)
    1025           22 :          RETURN
    1026              :       END IF
    1027              :       ! The byte bound also keeps all counts/displacements within default INTEGER.
    1028        39642 :       nblock = INT(nr(1))
    1029        39642 :       route%nvalue = INT(nr(2))
    1030            0 :       ALLOCATE (route%scount(2, 0:world%num_pe - 1), route%rcount(2, 0:world%num_pe - 1), &
    1031       237852 :                 route%sdisp(2, 0:world%num_pe - 1), route%rdisp(2, 0:world%num_pe - 1))
    1032       277494 :       route%scount(:, :) = INT(send_size)
    1033       277494 :       route%rcount(:, :) = INT(recv_size)
    1034        39642 :       IF (SIZE(owner) == 1) CALL inter%bcast(route%rcount, 0)
    1035       118926 :       route%sdisp(:, 0) = 0
    1036       118926 :       route%rdisp(:, 0) = 0
    1037        79284 :       DO rank = 1, world%num_pe - 1
    1038       118926 :          route%sdisp(:, rank) = route%sdisp(:, rank - 1) + route%scount(:, rank - 1)
    1039       158568 :          route%rdisp(:, rank) = route%rdisp(:, rank - 1) + route%rcount(:, rank - 1)
    1040              :       END DO
    1041            0 :       ALLOCATE (route%send_entry(INT(ns(1))), route%send_first(INT(ns(1)) + 1), &
    1042            0 :                 route%send_values(MERGE(0, INT(ns(2)), world%num_pe == 1)), send_metadata(8, INT(ns(1))), &
    1043            0 :                 position(2, 0:world%num_pe - 1), route%entry(8, nblock), &
    1044       631200 :                 route%first(nblock + 1))
    1045       277494 :       position(:, :) = route%sdisp
    1046      8385615 :       DO i = 1, route%traversal%nentry
    1047      8345973 :          nrow = row_size(route%traversal%row(i))
    1048      8345973 :          ncol = col_size(route%traversal%col(i))
    1049      8345973 :          CALL kpoint_operator_rs_owners(route, matrix_struct, i, needed)
    1050     20570339 :          DO p = 0, SIZE(owner) - 1
    1051     12184724 :             IF (.NOT. needed(p)) CYCLE
    1052     10120091 :             dest = owner(p)
    1053     10120091 :             iblock = position(1, dest) + 1
    1054     10120091 :             pos = position(2, dest) + 1
    1055     10120091 :             route%send_entry(iblock) = i
    1056     10120091 :             route%send_first(iblock) = pos
    1057     50600455 :             send_metadata(1:4, iblock) = [route%traversal%row_offset(i), route%traversal%col_offset(i), nrow, ncol]
    1058     40480364 :             send_metadata(5:7, iblock) = NINT(route%traversal%symmetry_sign(i))*route%traversal%cell(:, i)
    1059     10120091 :             send_metadata(8, iblock) = MERGE(1, 0, route%traversal%row(i) /= route%traversal%col(i))
    1060     40770879 :             position(:, dest) = position(:, dest) + [1, nrow*ncol]
    1061              :          END DO
    1062              :       END DO
    1063        39642 :       route%send_first(INT(ns(1)) + 1) = INT(ns(2)) + 1
    1064        39642 :       IF (SIZE(owner) == 1) THEN
    1065       139650 :          CALL world%allgatherv(send_metadata, route%entry, 8*route%rcount(1, :), 8*route%rdisp(1, :))
    1066              :       ELSE
    1067              :          CALL world%alltoall(send_metadata, 8*route%scount(1, :), 8*route%sdisp(1, :), &
    1068       105408 :                              route%entry, 8*route%rcount(1, :), 8*route%rdisp(1, :))
    1069        11712 :          CALL inter%bcast(route%entry, 0)
    1070              :       END IF
    1071        39642 :       route%first(1) = 1
    1072     14666955 :       DO i = 1, nblock
    1073     14666955 :          route%first(i + 1) = route%first(i) + route%entry(3, i)*route%entry(4, i)
    1074              :       END DO
    1075        39642 :       CPASSERT(route%first(nblock + 1) == route%nvalue + 1)
    1076        39642 :       route%matrix_struct => matrix_struct
    1077        39642 :       CALL cp_fm_struct_retain(route%matrix_struct)
    1078        39642 :       CALL kpoint_operator_rs_tile_map(route)
    1079        39642 :       IF (SIZE(owner) == 1 .AND. nblock > 0) THEN
    1080              :          ! build_neighbor_lists keeps each symmetric SAB pair's images contiguous
    1081              :          ! on its unique source rank. Packing preserves this traversal order, so
    1082              :          ! Allgatherv concatenates whole AO groups, even without global key ordering.
    1083     35973986 :          ngroup = 1 + COUNT(ANY(route%entry(1:2, 2:) /= route%entry(1:2, :nblock - 1), DIM=1))
    1084        83790 :          ALLOCATE (route%block_first(ngroup + 1))
    1085        27930 :          route%block_first(1) = 1
    1086        27930 :          igroup = 1
    1087      9014444 :          DO i = 2, nblock
    1088     26395976 :             IF (ALL(route%entry(1:2, i) == route%entry(1:2, i - 1))) CYCLE
    1089       358058 :             igroup = igroup + 1
    1090      9014444 :             route%block_first(igroup) = i
    1091              :          END DO
    1092        27930 :          route%block_first(ngroup + 1) = nblock + 1
    1093              :       END IF
    1094        39642 :       ready = .TRUE.
    1095        39642 :       CALL timestop(handle)
    1096              : 
    1097       118992 :    END SUBROUTINE kpoint_operator_rs_plan_create
    1098              : 
    1099              : ! **************************************************************************************************
    1100              : !> \brief Cache local intervals intersecting each routed block and its Hermitian mirror.
    1101              : !>        FM indices are ordered globally; dummy indices on empty tiles lie outside the matrix.
    1102              : !> \param route fixed routing and target distribution
    1103              : ! **************************************************************************************************
    1104        39642 :    SUBROUTINE kpoint_operator_rs_tile_map(route)
    1105              :       TYPE(kpoint_operator_rs_route_type), INTENT(INOUT) :: route
    1106              : 
    1107        79284 :       INTEGER :: c0, i, nc, nr, r0, rows(0:route%matrix_struct%nrow_global), &
    1108        39642 :          cols(0:route%matrix_struct%ncol_global)
    1109              : 
    1110      1004388 :       rows(:) = 0
    1111      1004388 :       cols(:) = 0
    1112       849280 :       DO i = 1, SIZE(route%matrix_struct%row_indices)
    1113       809638 :          r0 = route%matrix_struct%row_indices(i)
    1114       849280 :          IF (r0 >= 1 .AND. r0 <= UBOUND(rows, 1)) rows(r0) = 1
    1115              :       END DO
    1116       964746 :       DO i = 1, SIZE(route%matrix_struct%col_indices)
    1117       925104 :          c0 = route%matrix_struct%col_indices(i)
    1118       964746 :          IF (c0 >= 1 .AND. c0 <= UBOUND(cols, 1)) cols(c0) = 1
    1119              :       END DO
    1120       964746 :       DO i = 1, UBOUND(rows, 1)
    1121       964746 :          rows(i) = rows(i) + rows(i - 1)
    1122              :       END DO
    1123       964746 :       DO i = 1, UBOUND(cols, 1)
    1124       964746 :          cols(i) = cols(i) + cols(i - 1)
    1125              :       END DO
    1126        39642 :       route%nrow = rows(UBOUND(rows, 1))
    1127        39642 :       route%ncol = cols(UBOUND(cols, 1))
    1128       118926 :       ALLOCATE (route%tile(4, 2, SIZE(route%entry, 2)))
    1129     14666955 :       DO i = 1, SIZE(route%entry, 2)
    1130     14627313 :          r0 = route%entry(1, i)
    1131     14627313 :          c0 = route%entry(2, i)
    1132     14627313 :          nr = route%entry(3, i)
    1133     14627313 :          nc = route%entry(4, i)
    1134     73136565 :          route%tile(:, 1, i) = [rows(r0 - 1) + 1, rows(r0 + nr - 1), cols(c0 - 1) + 1, cols(c0 + nc - 1)]
    1135     73176207 :          route%tile(:, 2, i) = [rows(c0 - 1) + 1, rows(c0 + nc - 1), cols(r0 - 1) + 1, cols(r0 + nr - 1)]
    1136              :       END DO
    1137        39642 :    END SUBROUTINE kpoint_operator_rs_tile_map
    1138              : 
    1139              : ! **************************************************************************************************
    1140              : !> \brief Find group-local FM owners intersecting a stored block or its mirror.
    1141              : !> \param route source AO block layout and neighbor traversal
    1142              : !> \param fms target FM layout within each group
    1143              : !> \param i traversal entry
    1144              : !> \param needed recipient group-local ranks, without duplicate mirror sends
    1145              : ! **************************************************************************************************
    1146     16812066 :    SUBROUTINE kpoint_operator_rs_owners(route, fms, i, needed)
    1147              : 
    1148              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    1149              :       TYPE(cp_fm_struct_type), INTENT(IN)                :: fms
    1150              :       INTEGER, INTENT(IN)                                :: i
    1151              :       LOGICAL, INTENT(OUT)                               :: needed(0:)
    1152              : 
    1153              :       INTEGER                                            :: c0, j, nc, nr, pc, pr, r0
    1154     33624132 :       LOGICAL :: rows(2, 0:fms%context%num_pe(1) - 1), cols(2, 0:fms%context%num_pe(2) - 1)
    1155              : 
    1156     16812066 :       r0 = route%traversal%row_offset(i)
    1157     16812066 :       c0 = route%traversal%col_offset(i)
    1158     16812066 :       nr = route%row_size(route%traversal%row(i))
    1159     16812066 :       nc = route%col_size(route%traversal%col(i))
    1160     90641130 :       rows(:, :) = .FALSE.
    1161     67248264 :       cols(:, :) = .FALSE.
    1162    123750816 :       DO j = r0, r0 + nr - 1
    1163    106938750 :          rows(1, fms%g2p_row(j)) = .TRUE.
    1164    123750816 :          cols(2, fms%g2p_col(j)) = .TRUE.
    1165              :       END DO
    1166    120217624 :       DO j = c0, c0 + nc - 1
    1167    103405558 :          rows(2, fms%g2p_row(j)) = .TRUE.
    1168    120217624 :          cols(1, fms%g2p_col(j)) = .TRUE.
    1169              :       END DO
    1170     28342978 :       IF (route%traversal%row(i) == route%traversal%col(i)) rows(2, :) = .FALSE.
    1171     41421754 :       needed(:) = .FALSE.
    1172     33624132 :       DO pc = 0, fms%context%num_pe(2) - 1
    1173     58233820 :          DO pr = 0, fms%context%num_pe(1) - 1
    1174              :             needed(fms%context%blacs2mpi(pr, pc)) = &
    1175     45594525 :                (rows(1, pr) .AND. cols(1, pc)) .OR. (rows(2, pr) .AND. cols(2, pc))
    1176              :          END DO
    1177              :       END DO
    1178              : 
    1179     16812066 :    END SUBROUTINE kpoint_operator_rs_owners
    1180              : 
    1181              : ! **************************************************************************************************
    1182              : !> \brief Check the source AO layout, not its values or sparse block presence.
    1183              : !> \param route fixed context route
    1184              : !> \param source current operator images
    1185              : !> \param matrix_row row to capture
    1186              : !> \return whether the row has the route's image count and AO layout
    1187              : ! **************************************************************************************************
    1188        49460 :    FUNCTION kpoint_operator_rs_compatible(route, source, matrix_row) RESULT(compatible)
    1189              : 
    1190              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    1191              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
    1192              :       INTEGER, INTENT(IN)                                :: matrix_row
    1193              :       LOGICAL                                            :: compatible
    1194              : 
    1195              :       INTEGER                                            :: image
    1196        49460 :       INTEGER, POINTER                                   :: col_size(:), row_size(:)
    1197              : 
    1198        49460 :       compatible = .FALSE.
    1199        49460 :       IF (SIZE(source, 2) /= route%nimage) RETURN
    1200        49460 :       IF (matrix_row < 1 .OR. matrix_row > SIZE(source, 1)) RETURN
    1201      2882174 :       DO image = 1, SIZE(source, 2)
    1202      2832714 :          CALL dbcsr_get_info(source(matrix_row, image)%matrix, row_blk_size=row_size, col_blk_size=col_size)
    1203      2832714 :          IF (SIZE(row_size) /= SIZE(route%row_size) .OR. SIZE(col_size) /= SIZE(route%col_size)) RETURN
    1204     27722122 :          IF (ANY(row_size /= route%row_size) .OR. ANY(col_size /= route%col_size)) RETURN
    1205              :       END DO
    1206        49460 :       compatible = .TRUE.
    1207              : 
    1208        49460 :    END FUNCTION kpoint_operator_rs_compatible
    1209              : 
    1210              : ! **************************************************************************************************
    1211              : !> \brief Capture current values using cached routing, with no per-k communication.
    1212              : !> \param source current real-space operator images
    1213              : !> \param matrix_row row to snapshot
    1214              : !> \param route context-owned metadata, counts and reusable packing buffer
    1215              : !> \param kpoints initialized host parallel environment
    1216              : !> \param snapshot immutable tile-local values
    1217              : ! **************************************************************************************************
    1218        49460 :    SUBROUTINE kpoint_operator_rs_capture(source, matrix_row, route, kpoints, snapshot)
    1219              : 
    1220              :       TYPE(dbcsr_p_type), POINTER                        :: source(:, :)
    1221              :       INTEGER, INTENT(IN)                                :: matrix_row
    1222              :       TYPE(kpoint_operator_rs_route_type), INTENT(INOUT) :: route
    1223              :       TYPE(kpoint_type), POINTER                         :: kpoints
    1224              :       TYPE(kpoint_operator_rs_type), INTENT(OUT)         :: snapshot
    1225              : 
    1226              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_rs_capture'
    1227              : 
    1228              :       INTEGER                                            :: col, handle, i, iblock, ncol, nrow, pos, &
    1229              :                                                             row
    1230              :       LOGICAL                                            :: found
    1231        49460 :       REAL(KIND=dp), POINTER                             :: block(:, :)
    1232              :       TYPE(mp_para_env_type), POINTER                    :: inter, world
    1233              : 
    1234        49460 :       CALL timeset(routineN, handle)
    1235        49460 :       world => kpoints%blacs_env_all%para_env
    1236        49460 :       inter => kpoints%para_env_inter_kp
    1237     19403947 :       ALLOCATE (snapshot%blocks(SIZE(route%entry, 2)), snapshot%nonzero(SIZE(route%entry, 2)))
    1238        49460 :       snapshot%source => source
    1239        49460 :       snapshot%matrix_row = matrix_row
    1240        49460 :       IF (world%num_pe == 1) THEN
    1241              :          ! No value copy on a single world rank. Lookups happen before workers start.
    1242            0 :          DO iblock = 1, SIZE(route%send_entry)
    1243            0 :             i = route%send_entry(iblock)
    1244              :             CALL dbcsr_get_readonly_block_p(source(matrix_row, route%traversal%image(i))%matrix, &
    1245            0 :                                             route%traversal%row(i), route%traversal%col(i), block, found)
    1246            0 :             snapshot%nonzero(iblock) = found
    1247            0 :             IF (found) THEN
    1248            0 :                CPASSERT(SIZE(block, 1) == route%entry(3, iblock))
    1249            0 :                CPASSERT(SIZE(block, 2) == route%entry(4, iblock))
    1250            0 :                snapshot%blocks(iblock)%data => block
    1251              :             END IF
    1252              :          END DO
    1253            0 :          CALL timestop(handle)
    1254            0 :          RETURN
    1255              :       END IF
    1256       148380 :       ALLOCATE (snapshot%values(route%nvalue))
    1257    742191540 :       route%send_values(:) = 0.0_dp
    1258              : !$OMP PARALLEL DO DEFAULT(NONE) SHARED(source, matrix_row, route) &
    1259        49460 : !$OMP PRIVATE(iblock, i, block, found, nrow, ncol, pos, row, col) SCHEDULE(STATIC)
    1260              :       DO iblock = 1, SIZE(route%send_entry)
    1261              :          i = route%send_entry(iblock)
    1262              :          CALL dbcsr_get_readonly_block_p(source(matrix_row, route%traversal%image(i))%matrix, &
    1263              :                                          route%traversal%row(i), route%traversal%col(i), block, found)
    1264              :          IF (.NOT. found) CYCLE
    1265              :          nrow = route%row_size(route%traversal%row(i))
    1266              :          ncol = route%col_size(route%traversal%col(i))
    1267              :          CPASSERT(SIZE(block, 1) == nrow .AND. SIZE(block, 2) == ncol)
    1268              :          pos = route%send_first(iblock)
    1269              :          DO col = 1, ncol
    1270              :             DO row = 1, nrow
    1271              :                route%send_values(pos) = block(row, col)
    1272              :                pos = pos + 1
    1273              :             END DO
    1274              :          END DO
    1275              :       END DO
    1276              : !$OMP END PARALLEL DO
    1277        49460 :       IF (kpoints%para_env_kp%num_pe == 1) THEN
    1278       170830 :          CALL world%allgatherv(route%send_values, snapshot%values, route%rcount(2, :), route%rdisp(2, :))
    1279              :       ELSE
    1280              :          CALL world%alltoall(route%send_values, route%scount(2, :), route%sdisp(2, :), &
    1281       137646 :                              snapshot%values, route%rcount(2, :), route%rdisp(2, :))
    1282        15294 :          CALL inter%bcast(snapshot%values, 0)
    1283              :       END IF
    1284     19206107 :       DO iblock = 1, SIZE(route%entry, 2)
    1285     84110427 :          snapshot%nonzero(iblock) = ANY(snapshot%values(route%first(iblock):route%first(iblock + 1) - 1) /= 0.0_dp)
    1286              :          snapshot%blocks(iblock)%data(1:route%entry(3, iblock), 1:route%entry(4, iblock)) => &
    1287     19206107 :             snapshot%values(route%first(iblock):route%first(iblock + 1) - 1)
    1288              :       END DO
    1289        49460 :       snapshot%source => source
    1290        49460 :       snapshot%matrix_row = matrix_row
    1291        49460 :       CALL timestop(handle)
    1292              : 
    1293        98920 :    END SUBROUTINE kpoint_operator_rs_capture
    1294              : 
    1295              : ! **************************************************************************************************
    1296              : !> \brief Deliver one prepared operator, building a bounded window of local k points on a miss.
    1297              : !>        The callers retain their round/spin/slot ordering. Each row has its own
    1298              : !>        window so interleaved spins and H/S/T do not evict one another.
    1299              : !>        Only prepared values are read ahead; transfer and grid paths are unchanged.
    1300              : !> \param ctx serialized operator context
    1301              : !> \param irs captured row, possibly shared by spin-free instances
    1302              : !> \param slot prepared operator slot
    1303              : !> \param ik global k-point index owned by this group
    1304              : !> \param TARGET optional complex output on the prepared distribution
    1305              : !> \param fm_re optional real output instead of target; fm_im may be omitted for a real operator
    1306              : !> \param fm_im optional imaginary output on the same distribution
    1307              : ! **************************************************************************************************
    1308        35758 :    SUBROUTINE kpoint_operator_rs_build(ctx, irs, slot, ik, TARGET, fm_re, fm_im)
    1309              : 
    1310              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1311              :       INTEGER, INTENT(IN)                                :: irs, slot, ik
    1312              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
    1313              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1314              : 
    1315              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_rs_build'
    1316              : 
    1317              :       INTEGER                                            :: first, handle, k, last, ncol, nrow, width
    1318              :       INTEGER(KIND=int_8)                                :: bytes_per_k
    1319              :       LOGICAL                                            :: layout_ok
    1320        35758 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: xkp
    1321              : 
    1322        35758 :       CALL timeset(routineN, handle)
    1323        35758 :       IF (PRESENT(TARGET)) THEN
    1324        34380 :          layout_ok = cp_fm_struct_equivalent(target%matrix_struct, ctx%rs_route%matrix_struct)
    1325              :       ELSE
    1326         1378 :          CPASSERT(PRESENT(fm_re))
    1327         1378 :          layout_ok = cp_fm_struct_equivalent(fm_re%matrix_struct, ctx%rs_route%matrix_struct)
    1328         1378 :          IF (PRESENT(fm_im)) THEN
    1329         1080 :             layout_ok = layout_ok .AND. cp_fm_struct_equivalent(fm_im%matrix_struct, ctx%rs_route%matrix_struct)
    1330              :          END IF
    1331              :       END IF
    1332        34678 :       CPASSERT(layout_ok)
    1333              :       ! Avoid nested component array sections in GCC 16 bounds checks.
    1334        35758 :       xkp => ctx%kpoints%xkp
    1335        35758 :       first = ctx%kpoints%kp_range(1)
    1336        35758 :       last = ctx%kpoints%kp_range(2)
    1337        35758 :       CPASSERT(ik >= first .AND. ik <= last)
    1338        35758 :       nrow = ctx%rs_route%nrow
    1339        35758 :       ncol = ctx%rs_route%ncol
    1340        35758 :       bytes_per_k = 16_int_8*(INT(nrow, int_8)*ncol + SIZE(ctx%rs_route%entry, 2, KIND=int_8))
    1341              :       width = MIN(rs_batch_size, last - first + 1, &
    1342        35758 :                   INT(rs_batch_bytes/(INT(ctx%nspin*kpoint_num_slots, int_8)*MAX(1_int_8, bytes_per_k))))
    1343        35758 :       IF (ALLOCATED(ctx%rs_batch(irs, slot)%data)) width = SIZE(ctx%rs_batch(irs, slot)%data, 1)
    1344        35758 :       IF ((width < 2 .AND. .NOT. ALLOCATED(ctx%rs_batch(irs, slot)%data)) .OR. nrow == 0 .OR. ncol == 0) THEN
    1345              :          ! No extra dense tile for a large/empty layout or a single local k point.
    1346              :          CALL kpoint_operator_rs_build_batch(ctx%rs(irs, slot), ctx%rs_route, xkp(:, ik:ik), &
    1347        11431 :                                              TARGET=TARGET, fm_re=fm_re, fm_im=fm_im)
    1348              :       ELSE
    1349              :          ASSOCIATE (work => ctx%rs_batch(irs, slot))
    1350        26715 :             IF (.NOT. ALLOCATED(work%data)) ALLOCATE (work%data(width, nrow, ncol))
    1351        24327 :             IF (work%first == 0 .OR. ik < work%first .OR. ik >= work%first + width) THEN
    1352         5687 :                work%first = first + ((ik - first)/width)*width
    1353              :                CALL kpoint_operator_rs_build_batch(ctx%rs(irs, slot), ctx%rs_route, &
    1354         5687 :                                                    xkp(:, work%first:MIN(work%first + width - 1, last)), batch=work%data)
    1355              :             END IF
    1356        24327 :             k = ik - work%first + 1
    1357              :             ! Deliver a work copy: eigensolvers may overwrite H/S without corrupting the window.
    1358        48654 :             CALL kpoint_operator_rs_copy(work%data(k, :, :), TARGET, fm_re, fm_im)
    1359              :          END ASSOCIATE
    1360              :       END IF
    1361        35758 :       IF (ctx%kpoints%use_real_wfn) THEN
    1362          298 :          IF (PRESENT(TARGET)) target%local_data(:, :) = CMPLX(REAL(target%local_data, dp), 0.0_dp, dp)
    1363          298 :          IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
    1364              :       END IF
    1365        35758 :       CALL timestop(handle)
    1366              : 
    1367        35758 :    END SUBROUTINE kpoint_operator_rs_build
    1368              : 
    1369              : ! **************************************************************************************************
    1370              : !> \brief Accumulate several k points in one route/value traversal, without MPI or BLACS.
    1371              : !>        The leading batch dimension makes updates to different k points contiguous.
    1372              : !>        Columns remain independent OpenMP work items; each target retains the
    1373              : !>        original normal/mirrored contribution order. Phase scratch is call-local.
    1374              : !>        The singleton fallback writes directly to the supplied CFM or FM pair.
    1375              : !> \param snapshot immutable real-space contributions
    1376              : !> \param route structural block routing and target tile map
    1377              : !> \param xkp actual coordinates, one column per k point
    1378              : !> \param batch optional packed complex output (k point, local row, local column)
    1379              : !> \param TARGET optional single complex output instead of batch
    1380              : !> \param fm_re optional single real output instead of batch/target
    1381              : !> \param fm_im optional single imaginary output
    1382              : !> \param phase_work optional private phase storage supplied by a worker
    1383              : !> \param parallel_columns split columns across threads, disabled inside k-point workers
    1384              : ! **************************************************************************************************
    1385        20660 :    SUBROUTINE kpoint_operator_rs_build_batch(snapshot, route, xkp, batch, TARGET, fm_re, fm_im, phase_work, parallel_columns)
    1386              : 
    1387              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
    1388              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    1389              :       REAL(KIND=dp), INTENT(IN)                          :: xkp(:, :)
    1390              :       COMPLEX(KIND=dp), CONTIGUOUS, INTENT(INOUT), &
    1391              :          OPTIONAL, TARGET                                :: batch(:, :, :)
    1392              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
    1393              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1394              :       COMPLEX(KIND=dp), INTENT(INOUT), OPTIONAL, TARGET  :: phase_work(:, :)
    1395              :       LOGICAL, INTENT(IN), OPTIONAL                      :: parallel_columns
    1396              : 
    1397        20660 :       COMPLEX(KIND=dp), ALLOCATABLE, TARGET              :: owned_phase(:, :)
    1398        20660 :       COMPLEX(KIND=dp), CONTIGUOUS, POINTER              :: output(:, :, :)
    1399        20660 :       COMPLEX(KIND=dp), POINTER                          :: phase(:, :)
    1400              :       INTEGER                                            :: first, iblock, k, last, nbatch, ncol, &
    1401              :                                                             nthreads, previous, thread
    1402              :       LOGICAL                                            :: parallel
    1403              :       REAL(KIND=dp)                                      :: arg
    1404              : 
    1405        20660 :       nbatch = SIZE(xkp, 2)
    1406        20660 :       CPASSERT(nbatch >= 1 .AND. nbatch <= rs_batch_size .AND. SIZE(xkp, 1) == 3)
    1407        82640 :       CPASSERT(COUNT([PRESENT(batch), PRESENT(TARGET), PRESENT(fm_re)]) == 1)
    1408        20660 :       NULLIFY (output)
    1409        20660 :       IF (PRESENT(batch)) THEN
    1410         5687 :          CPASSERT(SIZE(batch, 1) >= nbatch)
    1411         5687 :          output => batch
    1412              :       ELSE
    1413        14973 :          CPASSERT(nbatch == 1)
    1414        14973 :          IF (PRESENT(TARGET)) THEN
    1415        14383 :             output(1:1, 1:SIZE(target%local_data, 1), 1:SIZE(target%local_data, 2)) => target%local_data
    1416              :          ELSE
    1417          590 :             CPASSERT(PRESENT(fm_re))
    1418      1213655 :             fm_re%local_data(:, :) = 0.0_dp
    1419       106129 :             IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
    1420              :          END IF
    1421              :       END IF
    1422        20660 :       IF (ASSOCIATED(output)) output(:, :, :) = z_zero
    1423        20660 :       IF (route%nrow == 0 .OR. route%ncol == 0) RETURN
    1424        20660 :       IF (PRESENT(phase_work)) THEN
    1425         3542 :          CPASSERT(SIZE(phase_work, 1) >= nbatch)
    1426         3542 :          CPASSERT(SIZE(phase_work, 2) == SIZE(route%entry, 2))
    1427         3542 :          phase => phase_work(1:nbatch, :)
    1428              :       ELSE
    1429        68472 :          ALLOCATE (owned_phase(nbatch, SIZE(route%entry, 2)))
    1430        17118 :          phase => owned_phase
    1431              :       END IF
    1432        20660 :       previous = 0
    1433      9754413 :       DO iblock = 1, SIZE(route%entry, 2)
    1434      9733753 :          IF (.NOT. snapshot%nonzero(iblock)) CYCLE
    1435              :          ! Neighboring blocks often share a lattice translation, including its symmetry sign.
    1436      9714507 :          IF (previous > 0) THEN
    1437     10095442 :             IF (ALL(route%entry(5:7, iblock) == route%entry(5:7, previous))) THEN
    1438        81947 :                phase(:, iblock) = phase(:, previous)
    1439              :                CYCLE
    1440              :             END IF
    1441              :          END IF
    1442     29087036 :          DO k = 1, nbatch
    1443     77637172 :             arg = twopi*DOT_PRODUCT(REAL(route%entry(5:7, iblock), dp), xkp(:, k))
    1444     29087036 :             phase(k, iblock) = CMPLX(COS(arg), SIN(arg), KIND=dp)
    1445              :          END DO
    1446        39906 :          previous = iblock
    1447              :       END DO
    1448        20660 :       ncol = route%ncol
    1449        20660 :       parallel = .TRUE.
    1450        20660 :       IF (PRESENT(parallel_columns)) parallel = parallel_columns
    1451        20660 :       nthreads = 1
    1452        20660 : !$    IF (parallel) nthreads = MIN(omp_get_max_threads(), ncol)
    1453        17118 :       IF (nthreads > 1) THEN
    1454              : !$OMP PARALLEL DEFAULT(NONE) NUM_THREADS(nthreads) SHARED(snapshot,route,phase,output,fm_re,fm_im,ncol) &
    1455              : !$OMP PRIVATE(first,last,thread,nthreads)
    1456              :          thread = 0
    1457              :          nthreads = 1
    1458              : !$       thread = omp_get_thread_num()
    1459              : !$       nthreads = omp_get_num_threads()
    1460              :          first = ncol*thread/nthreads + 1
    1461              :          last = ncol*(thread + 1)/nthreads
    1462            0 :          CALL kpoint_operator_rs_accumulate(snapshot, route, phase, first, last, output, fm_re, fm_im)
    1463              : !$OMP END PARALLEL
    1464              :       ELSE
    1465              :          ! Called inside a k-point worker: no nested parallel region, timer or allocation.
    1466        20660 :          CALL kpoint_operator_rs_accumulate(snapshot, route, phase, 1, ncol, output, fm_re, fm_im)
    1467              :       END IF
    1468              : 
    1469        41320 :    END SUBROUTINE kpoint_operator_rs_build_batch
    1470              : 
    1471              : ! **************************************************************************************************
    1472              : !> \brief Shared borrowed/packed phase-sum kernel; each caller owns a disjoint column interval.
    1473              : !> \param snapshot read-only block views
    1474              : !> \param route structural layout
    1475              : !> \param phase precomputed phases (k, block)
    1476              : !> \param first first local column owned by this call
    1477              : !> \param last last local column owned by this call
    1478              : !> \param output packed complex output, or disassociated for the FM pair
    1479              : !> \param fm_re real output
    1480              : !> \param fm_im imaginary output
    1481              : ! **************************************************************************************************
    1482        20660 :    SUBROUTINE kpoint_operator_rs_accumulate(snapshot, route, phase, first, last, output, fm_re, fm_im)
    1483              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
    1484              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    1485              :       COMPLEX(KIND=dp), INTENT(IN)                       :: phase(:, :)
    1486              :       INTEGER, INTENT(IN)                                :: first, last
    1487              :       COMPLEX(KIND=dp), CONTIGUOUS, INTENT(IN), POINTER  :: output(:, :, :)
    1488              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1489              : 
    1490              :       COMPLEX(KIND=dp)                                   :: factors(rs_batch_size), value
    1491        20660 :       COMPLEX(KIND=dp), CONTIGUOUS, POINTER              :: full(:, :)
    1492              :       INTEGER                                            :: c0, col, hi, iblock, j, k, lo, nc, nr, &
    1493              :                                                             part, r0, row, source_col, source_row
    1494              :       LOGICAL                                            :: full_fm
    1495              :       REAL(KIND=dp)                                      :: element, phase_im, phase_re
    1496              : 
    1497            0 :       IF (first > last) RETURN
    1498        20660 :       NULLIFY (full)
    1499              :       full_fm = .NOT. ASSOCIATED(output) .AND. &
    1500              :                 route%nrow == route%matrix_struct%nrow_global .AND. &
    1501        20660 :                 route%ncol == route%matrix_struct%ncol_global
    1502        20660 :       IF (ASSOCIATED(output)) THEN
    1503              :          IF (SIZE(output, 1) == 1 .AND. &
    1504        20070 :              route%nrow == route%matrix_struct%nrow_global .AND. &
    1505              :              route%ncol == route%matrix_struct%ncol_global) THEN
    1506         3719 :             full(1:SIZE(output, 2), 1:SIZE(output, 3)) => output
    1507              :          END IF
    1508              :       END IF
    1509      9754413 :       DO iblock = 1, SIZE(route%entry, 2)
    1510      9733753 :          IF (.NOT. snapshot%nonzero(iblock)) CYCLE
    1511      9714507 :          r0 = route%entry(1, iblock)
    1512      9714507 :          c0 = route%entry(2, iblock)
    1513      9714507 :          nr = route%entry(3, iblock)
    1514      9714507 :          nc = route%entry(4, iblock)
    1515      9714507 :          IF (full_fm) THEN
    1516              :             ! Single-rank split-FM caches have contiguous global rows and columns.
    1517              :             ! Keep each block's direct/mirrored order, without per-element index lookup.
    1518         7402 :             phase_re = REAL(phase(1, iblock), dp)
    1519         7402 :             phase_im = AIMAG(phase(1, iblock))
    1520         7402 :             lo = MAX(first, c0)
    1521         7402 :             hi = MIN(last, c0 + nc - 1)
    1522       103628 :             DO j = lo, hi
    1523       103628 : !$OMP SIMD PRIVATE(element)
    1524              :                DO row = 1, nr
    1525      1250938 :                   element = snapshot%blocks(iblock)%data(row, j - c0 + 1)
    1526      1250938 :                   fm_re%local_data(r0 + row - 1, j) = fm_re%local_data(r0 + row - 1, j) + phase_re*element
    1527      1250938 :                   IF (PRESENT(fm_im)) THEN
    1528      1250938 :                      fm_im%local_data(r0 + row - 1, j) = fm_im%local_data(r0 + row - 1, j) + phase_im*element
    1529              :                   END IF
    1530              :                END DO
    1531              :             END DO
    1532         7402 :             IF (route%entry(8, iblock) /= 0) THEN
    1533         3190 :                lo = MAX(first, r0)
    1534         3190 :                hi = MIN(last, r0 + nr - 1)
    1535        44660 :                DO j = lo, hi
    1536        44660 : !$OMP SIMD PRIVATE(element)
    1537              :                   DO row = 1, nc
    1538       539110 :                      element = snapshot%blocks(iblock)%data(j - r0 + 1, row)
    1539       539110 :                      fm_re%local_data(c0 + row - 1, j) = fm_re%local_data(c0 + row - 1, j) + phase_re*element
    1540       539110 :                      IF (PRESENT(fm_im)) THEN
    1541       539110 :                         fm_im%local_data(c0 + row - 1, j) = fm_im%local_data(c0 + row - 1, j) - phase_im*element
    1542              :                      END IF
    1543              :                   END DO
    1544              :                END DO
    1545              :             END IF
    1546              :             CYCLE
    1547              :          END IF
    1548      9707105 :          IF (ASSOCIATED(full)) THEN
    1549              :             ! Update whole contiguous blocks, clipped to this worker's columns.
    1550      1763319 :             lo = MAX(first, c0)
    1551      1763319 :             hi = MIN(last, c0 + nc - 1)
    1552      1763319 :             IF (lo <= hi) THEN
    1553              :                full(r0:r0 + nr - 1, lo:hi) = full(r0:r0 + nr - 1, lo:hi) + &
    1554    147298673 :                                              phase(1, iblock)*snapshot%blocks(iblock)%data(:, lo - c0 + 1:hi - c0 + 1)
    1555              :             END IF
    1556      1763319 :             IF (route%entry(8, iblock) /= 0) THEN
    1557      1067738 :                lo = MAX(first, r0)
    1558      1067738 :                hi = MIN(last, r0 + nr - 1)
    1559      1067738 :                IF (lo <= hi) THEN
    1560              :                   full(c0:c0 + nc - 1, lo:hi) = full(c0:c0 + nc - 1, lo:hi) + &
    1561              :                                                 CONJG(phase(1, iblock))* &
    1562     91307294 :                                                 TRANSPOSE(snapshot%blocks(iblock)%data(lo - r0 + 1:hi - r0 + 1, :))
    1563              :                END IF
    1564              :             END IF
    1565              :             CYCLE
    1566              :          END IF
    1567     23852018 :          DO part = 1, 2
    1568     15887572 :             IF (part == 2 .AND. route%entry(8, iblock) == 0) CYCLE
    1569     14130865 :             lo = MAX(first, route%tile(3, part, iblock))
    1570     14130865 :             hi = MIN(last, route%tile(4, part, iblock))
    1571     14130865 :             IF (lo > hi) CYCLE
    1572     45641866 :             factors(1:SIZE(phase, 1)) = phase(:, iblock)
    1573     27958111 :             IF (part == 2) factors(1:SIZE(phase, 1)) = CONJG(factors(1:SIZE(phase, 1)))
    1574    109776484 :             DO j = lo, hi
    1575     87682587 :                col = route%matrix_struct%col_indices(j)
    1576    615599898 :                DO row = route%tile(1, part, iblock), route%tile(2, part, iblock)
    1577    512029739 :                   source_row = route%matrix_struct%row_indices(row)
    1578    512029739 :                   source_col = col
    1579    512029739 :                   IF (part == 2) THEN
    1580    213867430 :                      source_col = source_row
    1581    213867430 :                      source_row = col
    1582              :                   END IF
    1583    512029739 :                   element = snapshot%blocks(iblock)%data(source_row - r0 + 1, source_col - c0 + 1)
    1584    599712326 :                   IF (ASSOCIATED(output)) THEN
    1585              : !$OMP SIMD
    1586              :                      DO k = 1, SIZE(phase, 1)
    1587   1038477198 :                         output(k, row, j) = output(k, row, j) + factors(k)*element
    1588              :                      END DO
    1589              :                   ELSE
    1590    135186823 :                      value = factors(1)*element
    1591    135186823 :                      fm_re%local_data(row, j) = fm_re%local_data(row, j) + REAL(value, dp)
    1592    135186823 :                      IF (PRESENT(fm_im)) fm_im%local_data(row, j) = fm_im%local_data(row, j) + AIMAG(value)
    1593              :                   END IF
    1594              :                END DO
    1595              :             END DO
    1596              :          END DO
    1597              :       END DO
    1598        20660 :    END SUBROUTINE kpoint_operator_rs_accumulate
    1599              : 
    1600              : ! **************************************************************************************************
    1601              : !> \brief Size per-worker phase scratch for direct assembly into a solver matrix.
    1602              : !> \param ctx prepared context
    1603              : !> \param nentry number of route entries
    1604              : ! **************************************************************************************************
    1605         1492 :    SUBROUTINE kpoint_operator_work_size(ctx, nentry)
    1606              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
    1607              :       INTEGER, INTENT(OUT)                               :: nentry
    1608              : 
    1609         1492 :       CPASSERT(ALLOCATED(ctx%rs_route))
    1610         1492 :       nentry = SIZE(ctx%rs_route%entry, 2)
    1611         1492 :    END SUBROUTINE kpoint_operator_work_size
    1612              : 
    1613              : ! **************************************************************************************************
    1614              : !> \brief Evaluate an immutable prepared operator with exclusively caller-owned output and scratch.
    1615              : !>        Prepare/refresh/release remain collective and serialized. Evaluate contains no MPI,
    1616              : !>        DBCSR lookup, window update or nested OpenMP region. Source blocks must stay unchanged.
    1617              : !> \param ctx prepared read-only context
    1618              : !> \param ispin instance spin
    1619              : !> \param slot operator slot
    1620              : !> \param ik global k index owned by this group
    1621              : !> \param phase private phase scratch, sized with work_size
    1622              : !> \param TARGET optional private complex singleton output
    1623              : !> \param fm_re optional private real output instead of TARGET
    1624              : !> \param fm_im optional private imaginary output, accompanying fm_re for a complex operator
    1625              : ! **************************************************************************************************
    1626         3542 :    SUBROUTINE kpoint_operator_evaluate(ctx, ispin, slot, ik, phase, TARGET, fm_re, fm_im)
    1627              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
    1628              :       INTEGER, INTENT(IN)                                :: ispin, slot, ik
    1629              :       TYPE(cp_cfm_type), INTENT(INOUT), OPTIONAL         :: target
    1630              :       COMPLEX(KIND=dp), INTENT(INOUT), TARGET            :: phase(:, :)
    1631              :       TYPE(cp_fm_type), INTENT(INOUT), OPTIONAL          :: fm_re, fm_im
    1632              : 
    1633         3542 :       REAL(KIND=dp), POINTER                             :: xkp(:, :)
    1634              :       LOGICAL                                            :: cached
    1635              :       INTEGER                                            :: k, row
    1636              : 
    1637            0 :       CPASSERT(ik >= ctx%kpoints%kp_range(1))
    1638         3542 :       CPASSERT(ik <= ctx%kpoints%kp_range(2))
    1639         3542 :       row = ispin
    1640         3542 :       IF (.NOT. ASSOCIATED(ctx%rs(row, slot)%source)) row = 1
    1641         3542 :       CPASSERT(ASSOCIATED(ctx%rs(row, slot)%source))
    1642         3542 :       xkp => ctx%kpoints%xkp
    1643         3542 :       k = ik - ctx%rs_batch(row, slot)%first + 1
    1644         3542 :       cached = .FALSE.
    1645         3542 :       IF (ALLOCATED(ctx%rs_batch(row, slot)%data)) cached = k >= 1 .AND. k <= SIZE(ctx%rs_batch(row, slot)%data, 1)
    1646              :       IF (cached) THEN
    1647            0 :          CALL kpoint_operator_rs_copy(ctx%rs_batch(row, slot)%data(k, :, :), TARGET, fm_re, fm_im)
    1648              :       ELSE
    1649              :          CALL kpoint_operator_rs_build_batch(ctx%rs(row, slot), ctx%rs_route, xkp(:, ik:ik), &
    1650              :                                              TARGET=TARGET, fm_re=fm_re, fm_im=fm_im, &
    1651         3542 :                                              phase_work=phase, parallel_columns=.FALSE.)
    1652              :       END IF
    1653         3542 :       IF (ctx%kpoints%use_real_wfn) THEN
    1654            0 :          IF (PRESENT(TARGET)) target%local_data(:, :) = CMPLX(REAL(target%local_data, dp), 0.0_dp, dp)
    1655            0 :          IF (PRESENT(fm_im)) fm_im%local_data(:, :) = 0.0_dp
    1656              :       END IF
    1657         3542 :    END SUBROUTINE kpoint_operator_evaluate
    1658              : 
    1659              : ! **************************************************************************************************
    1660              : !> \brief Start one operator for one global round and spin. The service walks
    1661              : !>        all groups with a k point in this round; groups without one still
    1662              : !>        participate in the transfers. Every rank must iterate through the
    1663              : !>        maximum local k-point count. Finish every started instance with
    1664              : !>        kpoint_operator_finish. The first start also creates the slot
    1665              : !>        bookkeeping of the context.
    1666              : !>        Collective over the environment-wide communicator: every rank must
    1667              : !>        call the same sequence of service calls.
    1668              : !> \param ctx context created by kpoint_operator_context_create
    1669              : !> \param ikp global round, up to the maximum k-point count across groups
    1670              : !> \param ispin instance spin: keys the bookkeeping
    1671              : !> \param rsmat real-space image matrices of the operator. A prepared operator
    1672              : !>        batch uses the values captured by prepare; otherwise start reads
    1673              : !>        the current values. The matching finish never reads rsmat
    1674              : !> \param slot instance slot, optionally backed by a prepared snapshot
    1675              : !> \param grid prepared reciprocal-grid cache of the caller
    1676              : !> \param use_grid extract from grid instead of a direct phase sum
    1677              : !> \param matrix_row row of rsmat to transform. Spin-free operators such
    1678              : !>        as S and T must pass kpoint_spin_free: their first matrix
    1679              : !>        dimension counts derivatives, not spins. A spin index is out of
    1680              : !>        bounds in plain SCF, but with derivatives stored it reads another
    1681              : !>        valid row silently
    1682              : ! **************************************************************************************************
    1683        30564 :    SUBROUTINE kpoint_operator_start(ctx, ikp, ispin, rsmat, slot, grid, use_grid, matrix_row)
    1684              : 
    1685              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1686              :       INTEGER, INTENT(IN)                                :: ikp, ispin
    1687              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1688              :       INTEGER, INTENT(IN)                                :: slot
    1689              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
    1690              :       LOGICAL, INTENT(IN)                                :: use_grid
    1691              :       INTEGER, INTENT(IN)                                :: matrix_row
    1692              : 
    1693              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_start'
    1694              : 
    1695              :       INTEGER                                            :: handle, igroup, ik, irs, kpmax
    1696        30564 :       INTEGER, DIMENSION(:, :), POINTER                  :: kp_dist
    1697              : 
    1698        30564 :       CALL timeset(routineN, handle)
    1699              : 
    1700        30564 :       CPASSERT(ASSOCIATED(ctx%kpoints))
    1701        30564 :       CPASSERT(ASSOCIATED(ctx%kpoints%sab_nl))
    1702              :       ! The instance spin is bounded by the spin space declared at creation;
    1703              :       ! the matrix row by this operator's own rows. The two bounds differ for
    1704              :       ! spin-free operators such as S, whose first dimension counts
    1705              :       ! real-space blocks.
    1706        30564 :       CPASSERT(ispin >= 1 .AND. ispin <= ctx%nspin)
    1707        30564 :       CPASSERT(matrix_row >= 1 .AND. matrix_row <= SIZE(rsmat, 1))
    1708        62066 :       kpmax = MAXVAL(ctx%kpoints%kp_dist(2, :) - ctx%kpoints%kp_dist(1, :) + 1)
    1709        30564 :       CPASSERT(ikp >= 1 .AND. ikp <= kpmax)
    1710        30564 :       CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
    1711              :       ! State is per round; allocate expensive copy descriptors only when
    1712              :       ! an actual distributed transfer is launched.
    1713        30564 :       IF (.NOT. ALLOCATED(ctx%status)) THEN
    1714        56850 :          ALLOCATE (ctx%status(kpmax, ctx%nspin, kpoint_num_slots))
    1715       164190 :          ctx%status(:, :, :) = kpoint_slot_idle
    1716              :       END IF
    1717        30564 :       CPASSERT(ctx%status(ikp, ispin, slot) == kpoint_slot_idle)
    1718        30564 :       IF (.NOT. use_grid) THEN
    1719        30564 :          irs = kpoint_operator_rs_match(ctx, rsmat, ispin, slot, matrix_row)
    1720        30564 :          IF (irs > 0) THEN
    1721        30524 :             CPASSERT(ASSOCIATED(ctx%work_sab, ctx%kpoints%sab_nl))
    1722        30524 :             ctx%status(ikp, ispin, slot) = kpoint_slot_local
    1723        30524 :             CALL timestop(handle)
    1724        30524 :             RETURN
    1725              :          END IF
    1726              :       END IF
    1727           40 :       CALL kpoint_operator_ensure(ctx, rsmat)
    1728              : 
    1729              :       ! Only the transfer backend needs copy descriptors. A prepared-only
    1730              :       ! or get-only context never carries them. Size by the declared spin
    1731              :       ! space, not by the possibly spin-free operator's row count.
    1732           40 :       IF (.NOT. ALLOCATED(ctx%info)) THEN
    1733              :          ! One copy_info per in-flight part: start takes it INTENT(OUT),
    1734              :          ! so two concurrent starts cannot share one instance.
    1735            0 :          ALLOCATE (ctx%info(SIZE(ctx%kpoints%xkp, 2), ctx%nspin, &
    1736          380 :                             kpoint_num_slots, MERGE(1, 2, ctx%kpoints%use_real_wfn)))
    1737              :       END IF
    1738              : 
    1739           40 :       kp_dist => ctx%kpoints%kp_dist
    1740           80 :       DO igroup = 1, ctx%kpoints%nkp_groups
    1741           40 :          ik = kp_dist(1, igroup) + ikp - 1
    1742           40 :          IF (ik > kp_dist(2, igroup)) CYCLE
    1743           40 :          CALL kpoint_operator_densify(ctx, rsmat, matrix_row, ik, grid, use_grid)
    1744              :          CALL kpoint_operator_launch(ctx%kpoints, ik, ctx%fmwork, ctx%fmlocal, &
    1745           80 :                                      ctx%info(ik, ispin, slot, :))
    1746              :       END DO
    1747           40 :       ctx%status(ikp, ispin, slot) = kpoint_slot_in_flight
    1748              : 
    1749           40 :       CALL timestop(handle)
    1750              : 
    1751        30564 :    END SUBROUTINE kpoint_operator_start
    1752              : 
    1753              : ! **************************************************************************************************
    1754              : !> \brief Finish one started instance: build its prepared group-local operator,
    1755              : !>        or merge received parts into cmat (real part first, overwrite then add)
    1756              : !>        and reclaim the send side on every group.
    1757              : !>        The slot returns to idle.
    1758              : !>        Collective over the environment-wide communicator: every rank must
    1759              : !>        call the same sequence of service calls.
    1760              : !> \param ctx context that started the instance
    1761              : !> \param ikp global round of the start call
    1762              : !> \param ispin spin component of the start call
    1763              : !> \param slot slot of the start call; the slot must be in flight
    1764              : !> \param cmat caller-allocated complex matrix that receives the operator.
    1765              : !>        It must share the distribution of the k-point group. Unchanged on
    1766              : !>        groups without a k point in this round
    1767              : !> \param fm_re optional real FM output instead of cmat for real wavefunctions, on the group layout
    1768              : ! **************************************************************************************************
    1769        30564 :    SUBROUTINE kpoint_operator_finish(ctx, ikp, ispin, slot, cmat, fm_re)
    1770              : 
    1771              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1772              :       INTEGER, INTENT(IN)                                :: ikp, ispin, slot
    1773              :       TYPE(cp_cfm_type), OPTIONAL                        :: cmat
    1774              :       TYPE(cp_fm_type), OPTIONAL                         :: fm_re
    1775              : 
    1776              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_finish'
    1777              : 
    1778              :       INTEGER                                            :: handle, igroup, ik, irs, kpmax
    1779              :       INTEGER, DIMENSION(2)                              :: kp_range
    1780        30564 :       INTEGER, DIMENSION(:, :), POINTER                  :: kp_dist
    1781              :       LOGICAL                                            :: layout_ok
    1782              : 
    1783        30564 :       CALL timeset(routineN, handle)
    1784              : 
    1785        30564 :       CPASSERT(ASSOCIATED(ctx%kpoints))
    1786        30564 :       CPASSERT(ALLOCATED(ctx%status))
    1787        30564 :       kpmax = SIZE(ctx%status, 1)
    1788        30564 :       CPASSERT(ikp >= 1 .AND. ikp <= kpmax)
    1789        30564 :       CPASSERT(ispin >= 1 .AND. ispin <= ctx%nspin)
    1790        30564 :       CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
    1791        30564 :       CPASSERT(ctx%status(ikp, ispin, slot) /= kpoint_slot_idle)
    1792              :       ! The merge writes cmat in place. A foreign distribution would fail
    1793              :       ! inside the BLACS calls, far from this argument.
    1794              : 
    1795        30564 :       CPASSERT(PRESENT(cmat) .NEQV. PRESENT(fm_re))
    1796        30564 :       CPASSERT(.NOT. PRESENT(fm_re) .OR. ctx%kpoints%use_real_wfn)
    1797        91692 :       kp_range = ctx%kpoints%kp_range
    1798        30564 :       IF (ctx%status(ikp, ispin, slot) == kpoint_slot_local) THEN
    1799        30524 :          irs = ispin
    1800        30524 :          IF (.NOT. ASSOCIATED(ctx%rs(irs, slot)%source)) irs = 1
    1801        30524 :          ik = kp_range(1) + ikp - 1
    1802        30524 :          IF (ik <= kp_range(2)) THEN
    1803        30520 :             CALL kpoint_operator_rs_build(ctx, irs, slot, ik, cmat, fm_re=fm_re)
    1804              :          END IF
    1805        30524 :          ctx%status(ikp, ispin, slot) = kpoint_slot_idle
    1806        30524 :          CALL timestop(handle)
    1807        30524 :          RETURN
    1808              :       END IF
    1809           40 :       CPASSERT(ctx%status(ikp, ispin, slot) == kpoint_slot_in_flight)
    1810           40 :       IF (PRESENT(cmat)) THEN
    1811           24 :          layout_ok = cp_fm_struct_equivalent(cmat%matrix_struct, ctx%fmlocal%matrix_struct)
    1812              :       ELSE
    1813           16 :          layout_ok = cp_fm_struct_equivalent(fm_re%matrix_struct, ctx%fmlocal%matrix_struct)
    1814              :       END IF
    1815           40 :       CPASSERT(layout_ok)
    1816           40 :       kp_dist => ctx%kpoints%kp_dist
    1817           80 :       DO igroup = 1, ctx%kpoints%nkp_groups
    1818           40 :          ik = kp_dist(1, igroup) + ikp - 1
    1819           40 :          IF (ik > kp_dist(2, igroup)) CYCLE
    1820              :          CALL kpoint_operator_receive(ctx%kpoints, ctx%fmlocal, ik, ctx%info(ik, ispin, slot, :), &
    1821           80 :                                       cmat=cmat, fm_re=fm_re)
    1822              :       END DO
    1823           40 :       ctx%status(ikp, ispin, slot) = kpoint_slot_idle
    1824              : 
    1825           40 :       CALL timestop(handle)
    1826              : 
    1827        30564 :    END SUBROUTINE kpoint_operator_finish
    1828              : 
    1829              : ! **************************************************************************************************
    1830              : !> \brief Assemble one operator for one global round and spin in one call:
    1831              : !>        run the single-group transfer of the service for every group and
    1832              : !>        finish it immediately, through transfer states local to this call.
    1833              : !>        The persistent slot machinery stays with start and finish.
    1834              : !>        Exactly one output flavor must be present: cmat receives the complex
    1835              : !>        operator (real part overwrites, imaginary part adds) through the
    1836              : !>        context buffer; the fm pair receives the two parts separately,
    1837              : !>        transferred directly into fm_re and fm_im on the owning group (any
    1838              : !>        distribution: the general copy redistributes); or the dbcsr pair
    1839              : !>        receives the two parts as block-cyclic matrices built by the service,
    1840              : !>        replacing the caller-side copy_fm_to_dbcsr_bc detour. The dbcsr
    1841              : !>        matrices must be freshly initialized (dbcsr_init_p) and the cmat
    1842              : !>        flavor must share the distribution of the k-point group.
    1843              : !>        Collective over the environment-wide communicator: every rank must
    1844              : !>        call the same sequence of service calls.
    1845              : !> \param ctx context created by kpoint_operator_context_create
    1846              : !> \param ikp global round, up to the maximum k-point count across groups
    1847              : !> \param ispin instance spin: bounds-checked against the context spin space
    1848              : !> \param rsmat real-space image matrices of the operator
    1849              : !> \param cmat complex matrix output
    1850              : !> \param fm_re real-part matrix output
    1851              : !> \param fm_im imaginary-part output; may be omitted for real wavefunctions
    1852              : !> \param mat_re real-part block-cyclic dbcsr output
    1853              : !> \param mat_im imaginary-part block-cyclic dbcsr output
    1854              : !> \param matrix_row row of rsmat to transform. Spin-free operators such
    1855              : !>        as S and T must pass kpoint_spin_free: their first matrix
    1856              : !>        dimension counts derivatives, not spins. A spin index is out of
    1857              : !>        bounds in plain SCF, but with derivatives stored it reads another
    1858              : !>        valid row silently
    1859              : !> \param slot optional prepared slot: opt in to its immutable snapshot.
    1860              : !>        Without slot, get always reads current source values. A missing or
    1861              : !>        mismatched snapshot retains the original transfer path. Prepared FM
    1862              : !>        targets must match the layout selected at prepare; inactive groups
    1863              : !>        do not inspect or modify their output arguments.
    1864              : !> \param grid optional lattice-FFT grid, overriding a prepared snapshot for this request
    1865              : ! **************************************************************************************************
    1866         5652 :    SUBROUTINE kpoint_operator_get(ctx, ikp, ispin, rsmat, cmat, fm_re, fm_im, &
    1867              :                                   mat_re, mat_im, matrix_row, slot, grid)
    1868              : 
    1869              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    1870              :       INTEGER, INTENT(IN)                                :: ikp, ispin
    1871              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    1872              :       TYPE(cp_cfm_type), OPTIONAL                        :: cmat
    1873              :       TYPE(cp_fm_type), OPTIONAL                         :: fm_re, fm_im
    1874              :       TYPE(dbcsr_type), INTENT(INOUT), OPTIONAL          :: mat_re, mat_im
    1875              :       INTEGER, INTENT(IN)                                :: matrix_row
    1876              :       INTEGER, INTENT(IN), OPTIONAL                      :: slot
    1877              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
    1878              : 
    1879              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_get'
    1880              : 
    1881              :       INTEGER                                            :: handle, igroup, ik, irs, kpmax
    1882         5652 :       INTEGER, DIMENSION(:, :), POINTER                  :: kp_dist
    1883              :       LOGICAL                                            :: layout_ok
    1884        67824 :       TYPE(copy_info_type)                               :: info(2)
    1885              : 
    1886         5652 :       CALL timeset(routineN, handle)
    1887              : 
    1888        22608 :       CPASSERT(COUNT([PRESENT(cmat), PRESENT(fm_re), PRESENT(mat_re)]) == 1)
    1889         5652 :       IF (PRESENT(fm_re) .OR. PRESENT(fm_im)) THEN
    1890          852 :          CPASSERT(PRESENT(fm_re))
    1891          852 :          CPASSERT(PRESENT(fm_im) .OR. ctx%kpoints%use_real_wfn)
    1892              :       END IF
    1893         5652 :       IF (PRESENT(mat_re) .OR. PRESENT(mat_im)) THEN
    1894          228 :          CPASSERT(PRESENT(mat_re) .AND. PRESENT(mat_im))
    1895              :       END IF
    1896              : 
    1897         5652 :       CPASSERT(ASSOCIATED(ctx%kpoints))
    1898         5652 :       CPASSERT(ASSOCIATED(ctx%kpoints%sab_nl))
    1899         5652 :       CPASSERT(ispin >= 1 .AND. ispin <= ctx%nspin)
    1900         5652 :       CPASSERT(matrix_row >= 1 .AND. matrix_row <= SIZE(rsmat, 1))
    1901        13780 :       kpmax = MAXVAL(ctx%kpoints%kp_dist(2, :) - ctx%kpoints%kp_dist(1, :) + 1)
    1902         5652 :       CPASSERT(ikp >= 1 .AND. ikp <= kpmax)
    1903         5652 :       IF (PRESENT(slot) .AND. .NOT. PRESENT(grid)) THEN
    1904         5240 :          CPASSERT(slot >= kpoint_slot_ks .AND. slot <= kpoint_slot_t)
    1905         5240 :          irs = kpoint_operator_rs_match(ctx, rsmat, ispin, slot, matrix_row)
    1906         5240 :          IF (irs > 0) THEN
    1907         5240 :             ik = ctx%kpoints%kp_range(1) + ikp - 1
    1908         5240 :             IF (ik <= ctx%kpoints%kp_range(2)) THEN
    1909         5238 :                IF (PRESENT(mat_re)) THEN
    1910          228 :                   IF (.NOT. ALLOCATED(ctx%fmimag)) THEN
    1911           56 :                      ALLOCATE (ctx%fmimag)
    1912           56 :                      CALL cp_fm_create(ctx%fmimag, ctx%fmlocal%matrix_struct)
    1913              :                   END IF
    1914          228 :                   CALL kpoint_operator_rs_build(ctx, irs, slot, ik, fm_re=ctx%fmlocal, fm_im=ctx%fmimag)
    1915          228 :                   CALL copy_fm_to_dbcsr_bc(ctx%fmlocal, mat_re)
    1916          228 :                   CALL copy_fm_to_dbcsr_bc(ctx%fmimag, mat_im)
    1917              :                ELSE
    1918         5010 :                   CALL kpoint_operator_rs_build(ctx, irs, slot, ik, TARGET=cmat, fm_re=fm_re, fm_im=fm_im)
    1919              :                END IF
    1920              :             END IF
    1921         5240 :             CALL timestop(handle)
    1922         5240 :             RETURN
    1923              :          END IF
    1924              :       END IF
    1925          412 :       CALL kpoint_operator_ensure(ctx, rsmat)
    1926          412 :       IF (PRESENT(cmat)) THEN
    1927              :          ! The merge writes cmat in place. A foreign distribution would fail
    1928              :          ! inside the BLACS calls, far from this argument.
    1929          412 :          layout_ok = cp_fm_struct_equivalent(cmat%matrix_struct, ctx%fmlocal%matrix_struct)
    1930          412 :          CPASSERT(layout_ok)
    1931              :       END IF
    1932              : 
    1933          412 :       kp_dist => ctx%kpoints%kp_dist
    1934          884 :       DO igroup = 1, ctx%kpoints%nkp_groups
    1935          472 :          ik = kp_dist(1, igroup) + ikp - 1
    1936          472 :          IF (ik > kp_dist(2, igroup)) CYCLE
    1937              :          ! Absent fm targets are forwarded as absent optional arguments. Both
    1938              :          ! CFM and DBCSR outputs use the same context-owned receive buffer.
    1939          472 :          CALL kpoint_operator_densify(ctx, rsmat, matrix_row, ik, grid=grid, use_grid=PRESENT(grid))
    1940              :          CALL kpoint_operator_launch(ctx%kpoints, ik, ctx%fmwork, ctx%fmlocal, info, &
    1941          472 :                                      target_re=fm_re, target_im=fm_im)
    1942              :          CALL kpoint_operator_receive(ctx%kpoints, ctx%fmlocal, ik, info, cmat=cmat, fm_re=fm_re, fm_im=fm_im, &
    1943          884 :                                       mat_re=mat_re, mat_im=mat_im)
    1944              :       END DO
    1945              : 
    1946          412 :       CALL timestop(handle)
    1947              : 
    1948         5652 :    END SUBROUTINE kpoint_operator_get
    1949              : 
    1950              : ! **************************************************************************************************
    1951              : !> \brief Read an operator inside a group-local OT loop, after collective cache preparation.
    1952              : !>        A single distributed group retains the sparse transform and distribution.
    1953              : !>        One-rank or multiple groups read their owned cache, without world collectives.
    1954              : !> \param matrix_rs current real-space operator
    1955              : !> \param kpoints host environment
    1956              : !> \param kp local k-point state
    1957              : !> \param ispin source row (one for spin-free S/T)
    1958              : !> \param cache_re prepared real part on the k-point group
    1959              : !> \param cache_im prepared imaginary part on the k-point group
    1960              : !> \param matrix_re newly initialized sparse real output
    1961              : !> \param matrix_im newly initialized sparse imaginary output
    1962              : ! **************************************************************************************************
    1963         6423 :    SUBROUTINE kpoint_operator_get_local(matrix_rs, kpoints, kp, ispin, cache_re, cache_im, &
    1964              :                                         matrix_re, matrix_im)
    1965              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: matrix_rs
    1966              :       TYPE(kpoint_type), POINTER                         :: kpoints
    1967              :       TYPE(kpoint_env_type), POINTER                     :: kp
    1968              :       INTEGER, INTENT(IN)                                :: ispin
    1969              :       TYPE(cp_fm_type), INTENT(IN)                       :: cache_re, cache_im
    1970              :       TYPE(dbcsr_type), POINTER                          :: matrix_re, matrix_im
    1971              : 
    1972              :       INTEGER                                            :: nkp_groups
    1973         6423 :       INTEGER, DIMENSION(:, :, :), POINTER               :: cell_to_index
    1974         6423 :       REAL(dp), DIMENSION(:, :), POINTER                 :: xkp
    1975              :       TYPE(neighbor_list_set_p_type), DIMENSION(:), &
    1976         6423 :          POINTER                                         :: sab_nl
    1977              : 
    1978         6423 :       NULLIFY (cell_to_index, sab_nl, xkp)
    1979            0 :       CPASSERT(ASSOCIATED(matrix_rs))
    1980         6423 :       CPASSERT(ispin >= 1 .AND. ispin <= SIZE(matrix_rs, 1))
    1981         6423 :       CPASSERT(ASSOCIATED(kpoints))
    1982         6423 :       CPASSERT(ASSOCIATED(kp))
    1983              : 
    1984         6423 :       CALL dbcsr_init_p(matrix_re)
    1985         6423 :       CALL dbcsr_init_p(matrix_im)
    1986         6423 :       nkp_groups = kpoints%nkp_groups
    1987         6423 :       xkp => kpoints%xkp
    1988         6423 :       cell_to_index => kpoints%cell_to_index
    1989         6423 :       sab_nl => kpoints%sab_nl
    1990         6423 :       IF (nkp_groups == 1 .AND. kpoints%para_env_kp%num_pe > 1) THEN
    1991         5360 :          CPASSERT(ASSOCIATED(xkp))
    1992         5360 :          CPASSERT(ASSOCIATED(cell_to_index))
    1993         5360 :          CPASSERT(ASSOCIATED(sab_nl))
    1994              :          CALL dbcsr_create(matrix_re, template=matrix_rs(1, 1)%matrix, &
    1995         5360 :                            matrix_type=dbcsr_type_symmetric)
    1996              :          CALL dbcsr_create(matrix_im, template=matrix_rs(1, 1)%matrix, &
    1997         5360 :                            matrix_type=dbcsr_type_antisymmetric)
    1998         5360 :          CALL cp_dbcsr_alloc_block_from_nbl(matrix_re, sab_nl)
    1999         5360 :          CALL cp_dbcsr_alloc_block_from_nbl(matrix_im, sab_nl)
    2000         5360 :          CALL dbcsr_set(matrix_re, 0.0_dp)
    2001         5360 :          CALL dbcsr_set(matrix_im, 0.0_dp)
    2002              :          CALL rskp_transform(rmatrix=matrix_re, cmatrix=matrix_im, rsmat=matrix_rs, ispin=ispin, &
    2003         5360 :                              xkp=xkp(1:3, kp%nkpoint), cell_to_index=cell_to_index, sab_nl=sab_nl)
    2004              :       ELSE
    2005         1063 :          CALL copy_fm_to_dbcsr_bc(cache_re, matrix_re)
    2006         1063 :          CALL copy_fm_to_dbcsr_bc(cache_im, matrix_im)
    2007              :       END IF
    2008         6423 :    END SUBROUTINE kpoint_operator_get_local
    2009              : 
    2010              : ! **************************************************************************************************
    2011              : !> \brief Fill caller-owned matrices for one operator row, including independent history snapshots.
    2012              : !>        Prepare and fallback transfers are collective. Prepared one-rank groups evaluate k points
    2013              : !>        concurrently, with no MPI or matrix allocation/refcounts inside the worker loop.
    2014              : !> \param kpoints host environment
    2015              : !> \param ao_ao_fm global-layout template
    2016              : !> \param rsmat current real-space images
    2017              : !> \param slot operator slot
    2018              : !> \param cmat allocated group-local outputs, one per locally owned k point
    2019              : !> \param matrix_row source row; S/T use kpoint_spin_free
    2020              : ! **************************************************************************************************
    2021         1190 :    SUBROUTINE kpoint_operator_get_all(kpoints, ao_ao_fm, rsmat, slot, cmat, matrix_row)
    2022              :       TYPE(kpoint_type), POINTER                         :: kpoints
    2023              :       TYPE(cp_fm_type), INTENT(IN)                       :: ao_ao_fm
    2024              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    2025              :       INTEGER, INTENT(IN)                                :: slot
    2026              :       TYPE(cp_cfm_type), DIMENSION(:), INTENT(INOUT)     :: cmat
    2027              :       INTEGER, INTENT(IN)                                :: matrix_row
    2028              : 
    2029              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_get_all'
    2030              : 
    2031         1190 :       COMPLEX(KIND=dp), ALLOCATABLE                      :: phase(:, :)
    2032              :       INTEGER                                            :: handle, ik, ikp, kplocal, kpmax, nentry, &
    2033              :                                                             nworkers
    2034              :       LOGICAL                                            :: layout_ok, prepared
    2035         1190 :       TYPE(kpoint_operator_context_type)                 :: ctx
    2036              : 
    2037         1190 :       CALL timeset(routineN, handle)
    2038         1190 :       kplocal = SIZE(kpoints%kp_env)
    2039         1190 :       CPASSERT(SIZE(cmat) == kplocal .AND. kplocal > 0)
    2040         1190 :       CALL kpoint_operator_context_create(ctx, kpoints, ao_ao_fm, 1)
    2041         1190 :       CALL kpoint_operator_prepare(ctx, rsmat, slot, prepared, matrix_row=matrix_row)
    2042         1190 :       IF (prepared .AND. kpoints%para_env_kp%num_pe == 1) THEN
    2043         3372 :          DO ikp = 1, kplocal
    2044         2336 :             layout_ok = cp_fm_struct_equivalent(cmat(ikp)%matrix_struct, ctx%rs_route%matrix_struct)
    2045         3372 :             CPASSERT(layout_ok)
    2046              :          END DO
    2047         1036 :          CALL kpoint_operator_work_size(ctx, nentry)
    2048         1036 :          nworkers = 1
    2049         1036 : !$       nworkers = MIN(omp_get_max_threads(), kplocal)
    2050              : !$OMP PARALLEL DEFAULT(NONE) NUM_THREADS(nworkers) SHARED(ctx,kpoints,cmat,slot,kplocal,nentry) &
    2051         1036 : !$OMP PRIVATE(phase,ikp,ik)
    2052              :          ALLOCATE (phase(1, nentry))
    2053              : !$OMP DO SCHEDULE(DYNAMIC, 1)
    2054              :          DO ikp = 1, kplocal
    2055              :             ik = kpoints%kp_range(1) + ikp - 1
    2056              :             CALL kpoint_operator_evaluate(ctx, 1, slot, ik, phase, cmat(ikp))
    2057              :          END DO
    2058              : !$OMP END DO
    2059         1036 :          DEALLOCATE (phase)
    2060              : !$OMP END PARALLEL
    2061              :       ELSE
    2062          308 :          kpmax = MAXVAL(kpoints%kp_dist(2, :) - kpoints%kp_dist(1, :) + 1)
    2063          398 :          DO ikp = 1, kpmax
    2064              :             CALL kpoint_operator_get(ctx, ikp, 1, rsmat, cmat=cmat(MIN(ikp, kplocal)), &
    2065          398 :                                      matrix_row=matrix_row, slot=slot)
    2066              :          END DO
    2067              :       END IF
    2068         1190 :       CALL kpoint_operator_context_release(ctx)
    2069         1190 :       CALL timestop(handle)
    2070         1190 :    END SUBROUTINE kpoint_operator_get_all
    2071              : 
    2072              : ! **************************************************************************************************
    2073              : !> \brief Borrow the group-local OT cache for one operator slot.
    2074              : !> \param kp owning k-point environment
    2075              : !> \param slot KS, overlap or kinetic operator slot
    2076              : !> \param ispin spin index, used only for the KS slot
    2077              : !> \return non-owning real/imaginary FM array
    2078              : ! **************************************************************************************************
    2079        37021 :    FUNCTION kpoint_operator_cache(kp, slot, ispin) RESULT(cache)
    2080              :       TYPE(kpoint_env_type), INTENT(IN)                  :: kp
    2081              :       INTEGER, INTENT(IN)                                :: slot, ispin
    2082              :       TYPE(cp_fm_type), POINTER                          :: cache(:)
    2083              : 
    2084        68371 :       SELECT CASE (slot)
    2085              :       CASE (kpoint_slot_ks)
    2086        31350 :          cache => kp%ot_hmat(:, ispin)
    2087              :       CASE (kpoint_slot_s)
    2088         3581 :          cache => kp%ot_smat
    2089              :       CASE (kpoint_slot_t)
    2090        37021 :          cache => kp%ot_tmat
    2091              :       END SELECT
    2092        37021 :    END FUNCTION kpoint_operator_cache
    2093              : 
    2094              : ! **************************************************************************************************
    2095              : !> \brief Sum images with block GEMMs over local k points, then fill each Hermitian mirror once.
    2096              : !>        Each worker owns one AO block and spin. Scratch uses the same packing
    2097              : !>        and k-tile upper bounds as the worker-count and tile-size choices.
    2098              : !> \param ctx prepared single-rank-group context and owned output caches
    2099              : !> \param slot operator slot
    2100              : !> \param nrows number of captured spin rows
    2101              : ! **************************************************************************************************
    2102         1548 :    SUBROUTINE kpoint_operator_store_blocks(ctx, slot, nrows)
    2103              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    2104              :       INTEGER, INTENT(IN)                                :: slot, nrows
    2105              : 
    2106              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_store_blocks'
    2107              : 
    2108              :       INTEGER                                            :: c0, col, first, handle, ic, igroup, ikp, &
    2109              :                                                             ispin, j, k, last, nbatch, nc, nelem, &
    2110              :                                                             ngroup, nimages, nk, nr, nworkers, r0, &
    2111              :                                                             row, width
    2112              :       INTEGER(KIND=int_8)                                :: nfactor, npack, work_size
    2113         1548 :       REAL(KIND=dp), ALLOCATABLE, TARGET                 :: work(:)
    2114         1548 :       REAL(KIND=dp), CONTIGUOUS, POINTER                 :: factors(:, :, :), RESULT(:, :, :), &
    2115         1548 :                                                             values(:, :)
    2116         1548 :       REAL(KIND=dp), POINTER                             :: xkp(:, :)
    2117         1548 :       TYPE(cp_fm_type), POINTER                          :: cache(:)
    2118              :       TYPE(kpoint_env_type), POINTER                     :: kp
    2119              : 
    2120         1548 :       CALL timeset(routineN, handle)
    2121         1548 :       nk = SIZE(ctx%kpoints%kp_env)
    2122         1548 :       nc = MERGE(1, 2, ctx%kpoints%use_real_wfn)
    2123         1548 :       xkp => ctx%kpoints%xkp
    2124              :       ASSOCIATE (kpoints => ctx%kpoints)
    2125              :          ! Clear padding as well as physical rows/columns, as in the per-k-point path.
    2126         4355 :          DO ikp = 1, nk
    2127         2807 :             kp => kpoints%kp_env(ikp)%kpoint_env
    2128         7734 :             DO ispin = 1, nrows
    2129         3379 :                cache => kpoint_operator_cache(kp, slot, ispin)
    2130        12944 :                DO ic = 1, nc
    2131      8142281 :                   cache(ic)%local_data(:, :) = 0.0_dp
    2132              :                END DO
    2133              :             END DO
    2134              :          END DO
    2135              :       END ASSOCIATE
    2136         1548 :       IF (nk == 0 .OR. SIZE(ctx%rs_route%entry, 2) == 0) THEN
    2137            0 :          CALL timestop(handle)
    2138              :          RETURN
    2139              :       END IF
    2140         1548 :       ngroup = SIZE(ctx%rs_route%block_first) - 1
    2141              :       nworkers = 1
    2142         1548 : !$    nworkers = omp_get_max_threads()
    2143         1548 :       nworkers = MIN(nworkers, ngroup*nrows)
    2144         1548 :       CALL kpoint_operator_batch_size(ctx, nworkers, 0, width, work_size)
    2145              : !$OMP PARALLEL DEFAULT(NONE) NUM_THREADS(nworkers) &
    2146              : !$OMP SHARED(ctx,slot,nrows,ngroup,nk,nc,width,work_size,xkp) &
    2147              : !$OMP PRIVATE(work,values,factors,result,igroup,ispin,first,last,nr,nelem,nimages,r0,c0,j,col,row, &
    2148         1548 : !$OMP         k,ikp,nbatch,ic,kp,cache,npack,nfactor)
    2149              :       ALLOCATE (work(work_size))
    2150              : !$OMP DO COLLAPSE(2) SCHEDULE(DYNAMIC, 1)
    2151              :       DO igroup = 1, ngroup
    2152              :          DO ispin = 1, nrows
    2153              :             first = ctx%rs_route%block_first(igroup)
    2154              :             last = ctx%rs_route%block_first(igroup + 1) - 1
    2155              :             r0 = ctx%rs_route%entry(1, first)
    2156              :             c0 = ctx%rs_route%entry(2, first)
    2157              :             nr = ctx%rs_route%entry(3, first)
    2158              :             nelem = nr*ctx%rs_route%entry(4, first)
    2159              :             IF (nelem == 0) CYCLE
    2160              :             IF (.NOT. ANY(ctx%rs(ispin, slot)%nonzero(first:last))) CYCLE
    2161              :             nimages = last - first + 1
    2162              :             npack = INT(nelem, int_8)*nimages
    2163              :             nfactor = INT(nimages, int_8)*width*nc
    2164              :             values(1:nelem, 1:nimages) => work(1:npack)
    2165              :             factors(1:nimages, 1:width, 1:nc) => work(npack + 1:npack + nfactor)
    2166              :             RESULT(1:nelem, 1:width, 1:nc) => work(npack + nfactor + 1:npack + nfactor + INT(nelem, int_8)*width*nc)
    2167              :             DO k = 1, nk, width
    2168              :                nbatch = MIN(width, nk - k + 1)
    2169              :                CALL kpoint_operator_contract_block(ctx%rs(ispin, slot), ctx%rs_route, first, last, &
    2170              :                                                    xkp(:, ctx%kpoints%kp_range(1) + k - 1: &
    2171              :                                                        ctx%kpoints%kp_range(1) + k + nbatch - 2), &
    2172              :                                                    values, factors, RESULT, pack=k == 1)
    2173              :                DO j = 1, nbatch
    2174              :                   ikp = k + j - 1
    2175              :                   kp => ctx%kpoints%kp_env(ikp)%kpoint_env
    2176              :                   cache => kpoint_operator_cache(kp, slot, ispin)
    2177              :                   DO ic = 1, nc
    2178              :                      DO col = 1, ctx%rs_route%entry(4, first)
    2179              : !$OMP SIMD
    2180              :                         DO row = 1, nr
    2181              :                            cache(ic)%local_data(r0 + row - 1, c0 + col - 1) = RESULT(row + (col - 1)*nr, j, ic)
    2182              :                         END DO
    2183              :                      END DO
    2184              :                      IF (ctx%rs_route%entry(8, first) == 0) CYCLE
    2185              :                      DO row = 1, nr
    2186              : !$OMP SIMD
    2187              :                         DO col = 1, ctx%rs_route%entry(4, first)
    2188              :                            cache(ic)%local_data(c0 + col - 1, r0 + row - 1) = &
    2189              :                               MERGE(1.0_dp, -1.0_dp, ic == kpoint_part_re)*RESULT(row + (col - 1)*nr, j, ic)
    2190              :                         END DO
    2191              :                      END DO
    2192              :                   END DO
    2193              :                END DO
    2194              :             END DO
    2195              :          END DO
    2196              :       END DO
    2197              : !$OMP END DO
    2198         1548 :       DEALLOCATE (work)
    2199              : !$OMP END PARALLEL
    2200         1548 :       CALL timestop(handle)
    2201         3096 :    END SUBROUTINE kpoint_operator_store_blocks
    2202              : 
    2203              : ! **************************************************************************************************
    2204              : !> \brief Size local k tiles, budgeting operator outputs and block scratch over all workers.
    2205              : !>        A block exceeding the budget is processed by one worker with one k point.
    2206              : !>        OT writes existing caches and distributes block/spin jobs; dense callers distribute k tiles.
    2207              : !> \param ctx prepared single-rank-group context
    2208              : !> \param nworkers requested workers, reduced to fit the minimum working set
    2209              : !> \param nmatrices simultaneously retained complex operator tiles; zero writes existing split-FM caches
    2210              : !> \param width number of k points per worker
    2211              : !> \param work_size real block scratch elements per worker
    2212              : ! **************************************************************************************************
    2213        26858 :    SUBROUTINE kpoint_operator_batch_size(ctx, nworkers, nmatrices, width, work_size)
    2214              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
    2215              :       INTEGER, INTENT(INOUT)                             :: nworkers
    2216              :       INTEGER, INTENT(IN)                                :: nmatrices
    2217              :       INTEGER, INTENT(OUT)                               :: width
    2218              :       INTEGER(KIND=int_8), INTENT(OUT)                   :: work_size
    2219              : 
    2220              :       INTEGER                                            :: first, igroup, nc, nk
    2221              :       INTEGER(KIND=int_8)                                :: max_pack, max_per_k, nelem, nimages, &
    2222              :                                                             output_bytes, per_k_bytes
    2223              : 
    2224        26858 :       CPASSERT(ctx%kpoints%para_env_kp%num_pe == 1)
    2225        26858 :       CPASSERT(nmatrices >= 0 .AND. nworkers > 0)
    2226        26858 :       max_pack = 0_int_8
    2227        26858 :       max_per_k = 0_int_8
    2228        26858 :       IF (SIZE(ctx%rs_route%entry, 2) > 0) THEN
    2229       410476 :          DO igroup = 1, SIZE(ctx%rs_route%block_first) - 1
    2230       383618 :             first = ctx%rs_route%block_first(igroup)
    2231       383618 :             nelem = INT(ctx%rs_route%entry(3, first), int_8)*ctx%rs_route%entry(4, first)
    2232       383618 :             nimages = ctx%rs_route%block_first(igroup + 1) - first
    2233       383618 :             max_pack = MAX(max_pack, nelem*nimages)
    2234       410476 :             max_per_k = MAX(max_per_k, nelem + nimages)
    2235              :          END DO
    2236              :       END IF
    2237              :       output_bytes = 16_int_8*nmatrices*ctx%rs_route%matrix_struct%local_leading_dimension* &
    2238        26858 :                      MAX(1, ctx%rs_route%ncol)
    2239        26858 :       nc = 2
    2240        26858 :       IF (nmatrices == 0) nc = MERGE(1, 2, ctx%kpoints%use_real_wfn)
    2241        26858 :       per_k_bytes = 8_int_8*nc*max_per_k + output_bytes
    2242        26858 :       nk = SIZE(ctx%kpoints%kp_env)
    2243        26858 :       IF (nmatrices > 0) nworkers = MIN(nworkers, nk)
    2244        26858 :       nworkers = MIN(nworkers, INT(MAX(1_int_8, rs_batch_bytes/(8_int_8*max_pack + per_k_bytes))))
    2245        26858 :       width = INT(MIN(INT(nk, int_8), MAX(1_int_8, (rs_batch_bytes/nworkers - 8_int_8*max_pack)/per_k_bytes)))
    2246        26858 :       IF (nmatrices > 0) width = MIN(width, (nk + nworkers - 1)/nworkers)
    2247        26858 :       work_size = max_pack + INT(nc, int_8)*width*max_per_k
    2248        26858 :    END SUBROUTINE kpoint_operator_batch_size
    2249              : 
    2250              : ! **************************************************************************************************
    2251              : !> \brief Assemble a caller-owned complex k tile using real block/image contractions.
    2252              : !>        The prepared route and snapshots are immutable; workers share no output or scratch.
    2253              : !> \param ctx prepared single-rank-group context
    2254              : !> \param ispin operator spin
    2255              : !> \param slot prepared operator slot
    2256              : !> \param first_k first global k index
    2257              : !> \param batch complex output (local row, local column, k point), including padding
    2258              : !> \param work private real scratch sized by kpoint_operator_batch_size
    2259              : ! **************************************************************************************************
    2260        28762 :    SUBROUTINE kpoint_operator_evaluate_batch(ctx, ispin, slot, first_k, batch, work)
    2261              :       TYPE(kpoint_operator_context_type), INTENT(IN)     :: ctx
    2262              :       INTEGER, INTENT(IN)                                :: ispin, slot, first_k
    2263              :       COMPLEX(KIND=dp), CONTIGUOUS, INTENT(OUT)          :: batch(:, :, :)
    2264              :       REAL(KIND=dp), CONTIGUOUS, INTENT(INOUT), TARGET   :: work(:)
    2265              : 
    2266              :       CHARACTER(len=*), PARAMETER :: routineN = 'kpoint_operator_evaluate_batch'
    2267              : 
    2268              :       INTEGER                                            :: c0, col, first, handle, igroup, j, last, &
    2269              :                                                             nc, nelem, nimages, nr, r0, row, spin, &
    2270              :                                                             width
    2271              :       INTEGER(KIND=int_8)                                :: nfactor, npack
    2272        28762 :       REAL(KIND=dp), CONTIGUOUS, POINTER                 :: factors(:, :, :), RESULT(:, :, :), &
    2273        28762 :                                                             values(:, :)
    2274        28762 :       REAL(KIND=dp), POINTER                             :: xkp(:, :)
    2275              : 
    2276        28762 :       CALL timeset(routineN, handle)
    2277        28762 :       CPASSERT(ctx%kpoints%para_env_kp%num_pe == 1)
    2278        28762 :       CPASSERT(SIZE(batch, 1) >= ctx%rs_route%nrow)
    2279        28762 :       CPASSERT(SIZE(batch, 2) >= ctx%rs_route%ncol)
    2280        28762 :       width = SIZE(batch, 3)
    2281        28762 :       CPASSERT(first_k >= ctx%kpoints%kp_range(1))
    2282        28762 :       CPASSERT(first_k + width - 1 <= ctx%kpoints%kp_range(2))
    2283        28762 :       spin = ispin
    2284        28762 :       IF (.NOT. ASSOCIATED(ctx%rs(spin, slot)%source)) spin = 1
    2285        28762 :       CPASSERT(ASSOCIATED(ctx%rs(spin, slot)%source))
    2286        28762 :       xkp => ctx%kpoints%xkp
    2287     61252339 :       batch(:, :, :) = z_zero
    2288        28762 :       IF (SIZE(ctx%rs_route%entry, 2) == 0) THEN
    2289            0 :          CALL timestop(handle)
    2290            0 :          RETURN
    2291              :       END IF
    2292       441074 :       DO igroup = 1, SIZE(ctx%rs_route%block_first) - 1
    2293       412312 :          first = ctx%rs_route%block_first(igroup)
    2294       412312 :          last = ctx%rs_route%block_first(igroup + 1) - 1
    2295       412920 :          IF (.NOT. ANY(ctx%rs(spin, slot)%nonzero(first:last))) CYCLE
    2296       412312 :          r0 = ctx%rs_route%entry(1, first)
    2297       412312 :          c0 = ctx%rs_route%entry(2, first)
    2298       412312 :          nr = ctx%rs_route%entry(3, first)
    2299       412312 :          nc = ctx%rs_route%entry(4, first)
    2300       412312 :          nelem = nr*nc
    2301       412312 :          IF (nelem == 0) CYCLE
    2302       412312 :          nimages = last - first + 1
    2303       412312 :          npack = INT(nelem, int_8)*nimages
    2304       412312 :          nfactor = 2_int_8*nimages*width
    2305       412312 :          CPASSERT(SIZE(work, KIND=int_8) >= npack + nfactor + 2_int_8*nelem*width)
    2306       412312 :          values(1:nelem, 1:nimages) => work(1:npack)
    2307       412312 :          factors(1:nimages, 1:width, 1:2) => work(npack + 1:npack + nfactor)
    2308       412312 :          RESULT(1:nelem, 1:width, 1:2) => work(npack + nfactor + 1:npack + nfactor + 2_int_8*nelem*width)
    2309              :          CALL kpoint_operator_contract_block(ctx%rs(spin, slot), ctx%rs_route, first, last, &
    2310              :                                              xkp(:, first_k:first_k + width - 1), &
    2311       412312 :                                              values, factors, RESULT, pack=.TRUE.)
    2312      1355586 :          DO j = 1, width
    2313      5574466 :             DO col = 1, nc
    2314      5574466 : !$OMP SIMD
    2315              :                DO row = 1, nr
    2316              :                   batch(r0 + row - 1, c0 + col - 1, j) = &
    2317     35375450 :                      CMPLX(RESULT(row + (col - 1)*nr, j, 1), RESULT(row + (col - 1)*nr, j, 2), KIND=dp)
    2318              :                END DO
    2319              :             END DO
    2320       914512 :             IF (ctx%rs_route%entry(8, first) == 0) CYCLE
    2321      4508432 :             DO row = 1, nr
    2322      4324581 : !$OMP SIMD
    2323              :                DO col = 1, nc
    2324     24445017 :                   batch(c0 + col - 1, r0 + row - 1, j) = CONJG(batch(r0 + row - 1, c0 + col - 1, j))
    2325              :                END DO
    2326              :             END DO
    2327              :          END DO
    2328              :       END DO
    2329        28762 :       CALL timestop(handle)
    2330        28762 :    END SUBROUTINE kpoint_operator_evaluate_batch
    2331              : 
    2332              : ! **************************************************************************************************
    2333              : !> \brief Contract one AO block's images over a k tile for real or complex destinations.
    2334              : !> \param snapshot immutable real-space values
    2335              : !> \param route shared AO-block geometry
    2336              : !> \param first first image entry
    2337              : !> \param last last image entry
    2338              : !> \param xkp k coordinates for this tile
    2339              : !> \param values packed real source block columns
    2340              : !> \param factors real cosine and optional sine factors
    2341              : !> \param RESULT real and optional imaginary output blocks
    2342              : !> \param pack pack source values on the first tile; later tiles reuse them
    2343              : ! **************************************************************************************************
    2344       429738 :    SUBROUTINE kpoint_operator_contract_block(snapshot, route, first, last, xkp, values, factors, RESULT, pack)
    2345              :       TYPE(kpoint_operator_rs_type), INTENT(IN)          :: snapshot
    2346              :       TYPE(kpoint_operator_rs_route_type), INTENT(IN)    :: route
    2347              :       INTEGER, INTENT(IN)                                :: first, last
    2348              :       REAL(KIND=dp), INTENT(IN)                          :: xkp(:, :)
    2349              :       REAL(KIND=dp), CONTIGUOUS, INTENT(INOUT)           :: values(:, :), factors(:, :, :)
    2350              :       REAL(KIND=dp), CONTIGUOUS, INTENT(OUT)             :: RESULT(:, :, :)
    2351              :       LOGICAL, INTENT(IN)                                :: pack
    2352              : 
    2353              :       INTEGER                                            :: col, iblock, ic, ik, nactive, nelem, nr, &
    2354              :                                                             row
    2355              :       REAL(KIND=dp)                                      :: arg
    2356              : 
    2357       429738 :       nelem = SIZE(values, 1)
    2358       429738 :       nr = route%entry(3, first)
    2359       429738 :       nactive = 0
    2360     11015152 :       DO iblock = first, last
    2361     10585414 :          IF (.NOT. snapshot%nonzero(iblock)) CYCLE
    2362     10560302 :          nactive = nactive + 1
    2363     10560302 :          IF (pack) THEN
    2364     79397728 :             DO col = 1, route%entry(4, first)
    2365     79397728 : !$OMP SIMD
    2366              :                DO row = 1, nr
    2367    583746658 :                   values(row + (col - 1)*nr, nactive) = snapshot%blocks(iblock)%data(row, col)
    2368              :                END DO
    2369              :             END DO
    2370              :          END IF
    2371     36083169 :          DO ik = 1, SIZE(xkp, 2)
    2372    100272068 :             arg = twopi*DOT_PRODUCT(REAL(route%entry(5:7, iblock), dp), xkp(:, ik))
    2373     25068017 :             factors(nactive, ik, 1) = COS(arg)
    2374     35628319 :             IF (SIZE(factors, 3) == 2) factors(nactive, ik, 2) = SIN(arg)
    2375              :          END DO
    2376              :       END DO
    2377      1289214 :       DO ic = 1, SIZE(factors, 3)
    2378              :          CALL dgemm('N', 'N', nelem, SIZE(xkp, 2), nactive, 1.0_dp, values, nelem, &
    2379      1289214 :                     factors(:, :, ic), SIZE(factors, 1), 0.0_dp, RESULT(:, :, ic), nelem)
    2380              :       END DO
    2381       429738 :    END SUBROUTINE kpoint_operator_contract_block
    2382              : 
    2383              : ! **************************************************************************************************
    2384              : !> \brief Prepare owned split-FM operators for OT without entering an SCF eigensolver.
    2385              : !>        H (or the initialization density) retains all source spins and is refreshed on each call.
    2386              : !>        S is stored once per context; the caller controls when to prepare T.
    2387              : !>        Outputs survive the context. Only routing and scratch persist between calls.
    2388              : !> \param ctx context with fixed geometry, AO layout and spin capacity
    2389              : !> \param matrix_ks Hamiltonian or initialization density
    2390              : !> \param matrix_s spin-free overlap
    2391              : !> \param matrix_t optional spin-free kinetic operator; absent leaves the existing kinetic cache alone
    2392              : ! **************************************************************************************************
    2393         1396 :    SUBROUTINE kpoint_operator_store(ctx, matrix_ks, matrix_s, matrix_t)
    2394              :       TYPE(kpoint_operator_context_type), INTENT(INOUT)  :: ctx
    2395              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: matrix_ks, matrix_s
    2396              :       TYPE(dbcsr_p_type), DIMENSION(:, :), OPTIONAL, &
    2397              :          POINTER                                         :: matrix_t
    2398              : 
    2399              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'kpoint_operator_store'
    2400              :       INTEGER                                            :: handle, ic, ikp, ispin, ncomp, nspin
    2401              :       LOGICAL                                            :: kinetic, reuse
    2402              :       TYPE(cp_fm_struct_type), POINTER                   :: matrix_struct
    2403              :       TYPE(kpoint_env_type), POINTER                     :: kp
    2404              :       TYPE(kpoint_type), POINTER                         :: kpoints
    2405              : 
    2406         1396 :       CALL timeset(routineN, handle)
    2407         1396 :       kpoints => ctx%kpoints
    2408         1396 :       nspin = SIZE(matrix_ks, 1)
    2409         1396 :       CPASSERT(nspin == 1 .OR. nspin == ctx%nspin)
    2410         1396 :       ncomp = MERGE(1, 2, kpoints%use_real_wfn)
    2411         1396 :       kinetic = PRESENT(matrix_t)
    2412         1396 :       IF (kinetic) kinetic = ASSOCIATED(matrix_t)
    2413         1396 :       CALL kpoint_operator_ensure_group(ctx)
    2414         1396 :       matrix_struct => ctx%fmlocal%matrix_struct
    2415         3924 :       DO ikp = 1, SIZE(kpoints%kp_env)
    2416         2528 :          kp => kpoints%kp_env(ikp)%kpoint_env
    2417         2528 :          reuse = .FALSE.
    2418         2528 :          IF (ASSOCIATED(kp%ot_hmat)) THEN
    2419         6915 :             IF (ALL(SHAPE(kp%ot_hmat) == [ncomp, nspin])) THEN
    2420         2305 :                reuse = cp_fm_struct_equivalent(kp%ot_hmat(1, 1)%matrix_struct, matrix_struct)
    2421              :             END IF
    2422              :          END IF
    2423         2305 :          IF (.NOT. reuse) THEN
    2424          223 :             CALL cp_fm_release(kp%ot_hmat)
    2425         1696 :             ALLOCATE (kp%ot_hmat(ncomp, nspin))
    2426          491 :             DO ispin = 1, nspin
    2427         1027 :                DO ic = 1, ncomp
    2428          804 :                   CALL cp_fm_create(kp%ot_hmat(ic, ispin), matrix_struct)
    2429              :                END DO
    2430              :             END DO
    2431              :          END IF
    2432         2528 :          CALL allocate_spin_free(kp%ot_smat)
    2433         3924 :          IF (kinetic) CALL allocate_spin_free(kp%ot_tmat)
    2434              :       END DO
    2435         1396 :       CALL store_slot(matrix_ks, kpoint_slot_ks, nspin)
    2436         1396 :       IF (.NOT. ctx%overlap_stored) THEN
    2437          368 :          CALL store_slot(matrix_s, kpoint_slot_s, 1)
    2438          368 :          ctx%overlap_stored = .TRUE.
    2439              :       END IF
    2440         1396 :       IF (kinetic) CALL store_slot(matrix_t, kpoint_slot_t, 1)
    2441         1396 :       CALL timestop(handle)
    2442              : 
    2443              :    CONTAINS
    2444              : 
    2445              : ! **************************************************************************************************
    2446              : !> \brief ...
    2447              : !> \param cache ...
    2448              : ! **************************************************************************************************
    2449         2868 :       SUBROUTINE allocate_spin_free(cache)
    2450              :       TYPE(cp_fm_type), DIMENSION(:), POINTER            :: cache
    2451              : 
    2452              :       INTEGER                                            :: ic
    2453              :       LOGICAL                                            :: reuse
    2454              : 
    2455         2868 :          reuse = .FALSE.
    2456         2868 :          IF (ASSOCIATED(cache)) THEN
    2457         2571 :             IF (SIZE(cache) == ncomp) reuse = cp_fm_struct_equivalent(cache(1)%matrix_struct, matrix_struct)
    2458              :          END IF
    2459         2571 :          IF (reuse) RETURN
    2460          297 :          CALL cp_fm_release(cache)
    2461         1485 :          ALLOCATE (cache(ncomp))
    2462          891 :          DO ic = 1, ncomp
    2463          891 :             CALL cp_fm_create(cache(ic), matrix_struct)
    2464              :          END DO
    2465              :       END SUBROUTINE allocate_spin_free
    2466              : 
    2467              : ! **************************************************************************************************
    2468              : !> \brief ...
    2469              : !> \param rsmat ...
    2470              : !> \param slot ...
    2471              : !> \param nrows ...
    2472              : ! **************************************************************************************************
    2473         1946 :       SUBROUTINE store_slot(rsmat, slot, nrows)
    2474              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    2475              :       INTEGER, INTENT(IN)                                :: slot, nrows
    2476              : 
    2477              :       INTEGER                                            :: fft_ready, ikp, ispin, kpmax
    2478              :       LOGICAL                                            :: prepared, ready, use_blocks, use_grid
    2479         1946 :       TYPE(rskp_grid_type), ALLOCATABLE                  :: grids(:)
    2480              : 
    2481              :          use_grid = kpoints%lattice_fft == lattice_fft_on .OR. &
    2482         1946 :                     (kpoints%lattice_fft == lattice_fft_auto .AND. SIZE(kpoints%xkp, 2) >= 27)
    2483         1946 :          IF (slot == kpoint_slot_ks .AND. nrows == ctx%nspin) THEN
    2484         1396 :             CALL kpoint_operator_prepare(ctx, rsmat, slot, prepared)
    2485              :          ELSE
    2486          550 :             CALL kpoint_operator_prepare(ctx, rsmat, slot, prepared, matrix_row=kpoint_spin_free)
    2487              :          END IF
    2488              :          ! Keep block GEMMs for direct assembly; get copies any prepared FFT output.
    2489         1946 :          use_blocks = prepared .AND. kpoints%para_env_kp%num_pe == 1
    2490         1946 :          IF (prepared) THEN
    2491         4260 :             DO ispin = 1, nrows
    2492         4704 :                use_blocks = use_blocks .AND. .NOT. ALLOCATED(ctx%rs_batch(ispin, slot)%data)
    2493              :             END DO
    2494              :          END IF
    2495         1946 :          IF (use_blocks) THEN
    2496         1548 :             CALL kpoint_operator_store_blocks(ctx, slot, nrows)
    2497              :          ELSE
    2498          796 :             kpmax = MAXVAL(kpoints%kp_dist(2, :) - kpoints%kp_dist(1, :) + 1)
    2499              :             ! Keep the existing forward lattice-FFT choice when distributed snapshots cannot be prepared.
    2500          398 :             use_grid = use_grid .AND. .NOT. prepared
    2501              :             IF (use_grid) THEN
    2502            0 :                CALL kpoint_operator_ensure(ctx, rsmat)
    2503            0 :                ALLOCATE (grids(nrows))
    2504            0 :                DO ispin = 1, nrows
    2505              :                   CALL rskp_transform_grid_prepare(grids(ispin), ctx%rmatrix, rsmat, ispin, &
    2506              :                                                    kpoints%xkp, kpoints%nkp_grid, kpoints%cell_to_index, &
    2507              :                                                    kpoints%sab_nl, ready, &
    2508            0 :                                                    max_storage_bytes=512_int_8*1024_int_8**2/INT(nspin + 1, int_8))
    2509            0 :                   use_grid = use_grid .AND. ready
    2510              :                END DO
    2511            0 :                fft_ready = MERGE(1, 0, use_grid)
    2512            0 :                CALL kpoints%blacs_env_all%para_env%min(fft_ready)
    2513            0 :                use_grid = fft_ready == 1
    2514              :             END IF
    2515         1142 :             DO ikp = 1, kpmax
    2516         1932 :                DO ispin = 1, nrows
    2517         1534 :                   IF (use_grid) THEN
    2518            0 :                      CALL store_round(rsmat, slot, ikp, ispin, grid=grids(ispin))
    2519              :                   ELSE
    2520          790 :                      CALL store_round(rsmat, slot, ikp, ispin)
    2521              :                   END IF
    2522              :                END DO
    2523              :             END DO
    2524          398 :             IF (ALLOCATED(grids)) THEN
    2525            0 :                DO ispin = 1, nrows
    2526            0 :                   CALL rskp_transform_grid_release(grids(ispin))
    2527              :                END DO
    2528            0 :                DEALLOCATE (grids)
    2529              :             END IF
    2530              :          END IF
    2531              :          ! The split-FM caches now own the results. Do not retain source block
    2532              :          ! pointers or duplicate snapshots across Hamiltonian rebuilds.
    2533         1946 :          IF (ALLOCATED(ctx%rs)) THEN
    2534         4354 :             DO ispin = 1, ctx%nspin
    2535         2408 :                CALL kpoint_operator_rs_clear(ctx%rs(ispin, slot))
    2536         2408 :                IF (ALLOCATED(ctx%rs_batch(ispin, slot)%data)) DEALLOCATE (ctx%rs_batch(ispin, slot)%data)
    2537         4354 :                ctx%rs_batch(ispin, slot)%first = 0
    2538              :             END DO
    2539              :          END IF
    2540         1946 :       END SUBROUTINE store_slot
    2541              : 
    2542              : ! **************************************************************************************************
    2543              : !> \brief ...
    2544              : !> \param rsmat ...
    2545              : !> \param slot ...
    2546              : !> \param ikp ...
    2547              : !> \param ispin ...
    2548              : !> \param grid ...
    2549              : ! **************************************************************************************************
    2550          790 :       SUBROUTINE store_round(rsmat, slot, ikp, ispin, grid)
    2551              :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: rsmat
    2552              :       INTEGER, INTENT(IN)                                :: slot, ikp, ispin
    2553              :       TYPE(rskp_grid_type), INTENT(IN), OPTIONAL         :: grid
    2554              : 
    2555          790 :       TYPE(cp_fm_type), DIMENSION(:), POINTER            :: cache
    2556              :       TYPE(cp_fm_type), POINTER                          :: imag
    2557              :       TYPE(kpoint_env_type), POINTER                     :: kp
    2558              : 
    2559              :          ! A valid dummy cache is passed on inactive tail groups; get leaves it untouched.
    2560          790 :          kp => kpoints%kp_env(MIN(ikp, SIZE(kpoints%kp_env)))%kpoint_env
    2561         1580 :          cache => kpoint_operator_cache(kp, slot, ispin)
    2562          790 :          NULLIFY (imag)
    2563          790 :          IF (SIZE(cache) == 2) imag => cache(2)
    2564              :          CALL kpoint_operator_get(ctx, ikp, ispin, rsmat, fm_re=cache(1), fm_im=imag, &
    2565          790 :                                   matrix_row=ispin, slot=slot, grid=grid)
    2566          790 :       END SUBROUTINE store_round
    2567              :    END SUBROUTINE kpoint_operator_store
    2568              : 
    2569            0 : END MODULE qs_kpoint_operators
        

Generated by: LCOV version 2.0-1