LCOV - code coverage report
Current view: top level - src - qs_ot_kp.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:123285f) Lines: 94.7 % 1347 1276
Test Date: 2026-11-04 00:58:17 Functions: 98.0 % 51 50

            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 Complex k-point orbital transformations, response kernels and local batch execution.
      10              : !>        Shared real/spectral operations are provided by qs_ot.
      11              : ! **************************************************************************************************
      12              : MODULE qs_ot_kp
      13              :    USE cp_cfm_diag,                     ONLY: cp_cfm_diag_workspace_type,&
      14              :                                               cp_cfm_heevd_local,&
      15              :                                               cp_cfm_local_workspace_prepare
      16              :    USE cp_cfm_types,                    ONLY: cp_cfm_p_type
      17              :    USE cp_dbcsr_api,                    ONLY: &
      18              :         dbcsr_add, dbcsr_copy, dbcsr_create, dbcsr_distribution_type, dbcsr_filter, &
      19              :         dbcsr_finalize, dbcsr_get_block_p, dbcsr_get_info, dbcsr_iterator_blocks_left, &
      20              :         dbcsr_iterator_next_block, dbcsr_iterator_start, dbcsr_iterator_stop, dbcsr_iterator_type, &
      21              :         dbcsr_multiply, dbcsr_p_type, dbcsr_release, dbcsr_reserve_blocks, dbcsr_scale, dbcsr_set, &
      22              :         dbcsr_transposed, dbcsr_type, dbcsr_type_no_symmetry
      23              :    USE cp_dbcsr_contrib,                ONLY: dbcsr_add_on_diag,&
      24              :                                               dbcsr_frobenius_norm,&
      25              :                                               dbcsr_hadamard_product,&
      26              :                                               dbcsr_reserve_all_blocks,&
      27              :                                               dbcsr_scale_by_vector
      28              :    USE cp_dbcsr_diag,                   ONLY: cp_dbcsr_heevd
      29              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_local,&
      30              :                                               copy_local_to_dbcsr
      31              :    USE cp_fm_diag,                      ONLY: FM_DIAG_TYPE_DLAF,&
      32              :                                               diag_lib_explicit,&
      33              :                                               diag_type
      34              :    USE cp_fm_types,                     ONLY: cp_fm_p_type
      35              :    USE kinds,                           ONLY: dp
      36              :    USE local_gemm_api,                  ONLY: LOCAL_GEMM_PU_GPU,&
      37              :                                               LOCAL_GEMM_PU_HOST,&
      38              :                                               local_gemm_ctxt_type
      39              :    USE mathlib,                         ONLY: diag_complex,&
      40              :                                               diamat_all,&
      41              :                                               gemm_square
      42              :    USE preconditioner,                  ONLY: apply_preconditioner,&
      43              :                                               apply_preconditioner_complex_batch
      44              :    USE preconditioner_types,            ONLY: preconditioner_p_type
      45              :    USE qs_ot,                           ONLY: qs_ot_complex_multiply,&
      46              :                                               qs_ot_p2m_diag,&
      47              :                                               qs_ot_project_gradient_real => qs_ot_project_gradient,&
      48              :                                               qs_ot_ref_update,&
      49              :                                               qs_ot_spectral_kernel,&
      50              :                                               qs_ot_spectral_values,&
      51              :                                               qs_ot_square_transpose
      52              :    USE qs_ot_types,                     ONLY: qs_ot_type
      53              : 
      54              : !$ USE OMP_LIB, ONLY: omp_get_max_threads
      55              : #include "./base/base_uses.f90"
      56              : 
      57              :    IMPLICIT NONE
      58              :    PRIVATE
      59              : 
      60              :    PUBLIC :: qs_ot_get_p_complex
      61              :    PUBLIC :: qs_ot_get_orbitals_complex
      62              :    PUBLIC :: qs_ot_get_orbitals_local_batch
      63              :    PUBLIC :: qs_ot_get_derivative_complex
      64              :    PUBLIC :: qs_ot_get_derivative_local_batch
      65              :    PUBLIC :: qs_ot_prepare_complex_tangent_metric
      66              :    PUBLIC :: qs_ot_prepare_complex_tangent_metric_batch
      67              :    PUBLIC :: qs_ot_get_orbitals_ref_complex
      68              :    PUBLIC :: qs_ot_get_derivative_ref_complex
      69              :    PUBLIC :: qs_ot_apply_complex_frechet_dbcsr
      70              :    PUBLIC :: qs_ot_antihermitian_spectral_norm
      71              :    PUBLIC :: qs_ot_complex_exp_frechet_kernel
      72              :    PUBLIC :: qs_ot_density_secant_orbital_overlaps
      73              :    PUBLIC :: qs_ot_density_secant_projected_hessian
      74              :    PUBLIC :: qs_ot_density_tangent
      75              :    PUBLIC :: qs_ot_fixed_n_energy_gradient
      76              :    PUBLIC :: qs_ot_fixed_n_multigroup_schur_block
      77              :    PUBLIC :: qs_ot_fixed_n_response_mu_shift
      78              :    PUBLIC :: qs_ot_finite_rotation_response
      79              :    PUBLIC :: qs_ot_projected_response_update
      80              :    PUBLIC :: qs_ot_symmetric_abs_solve
      81              :    PUBLIC :: qs_ot_project_gradient
      82              : 
      83              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_ot_kp'
      84              : 
      85              :    INTERFACE qs_ot_complex_multiply_local
      86              :       MODULE PROCEDURE qs_ot_complex_multiply_pair_local, qs_ot_complex_multiply_parts_local
      87              :    END INTERFACE
      88              : 
      89              :    ! Private rotation spectrum and scratch, reused across response evaluations.
      90              :    TYPE qs_ot_rotation_work_type
      91              :       INTEGER                                            :: info = 0
      92              :       TYPE(cp_cfm_diag_workspace_type)                   :: solver = cp_cfm_diag_workspace_type()
      93              :       COMPLEX(dp), ALLOCATABLE                           :: vectors(:, :), u(:, :), work(:, :), &
      94              :                                                             inner(:, :)
      95              :    END TYPE qs_ot_rotation_work_type
      96              : 
      97              :    ! Transient per-worker storage. The last index is 1 for real and 2 for imaginary.
      98              :    TYPE qs_ot_local_work_type
      99              :       INTEGER                                            :: n = 0, k = 0, info = 0
     100              :       LOGICAL                                            :: do_rotation = .FALSE., filtered = .FALSE.
     101              :       TYPE(local_gemm_ctxt_type)                         :: gemm_ctx = local_gemm_ctxt_type()
     102              :       TYPE(cp_cfm_diag_workspace_type)                   :: solver = cp_cfm_diag_workspace_type()
     103              :       TYPE(qs_ot_rotation_work_type)                     :: rotation = qs_ot_rotation_work_type()
     104              :       COMPLEX(dp), ALLOCATABLE                           :: vectors(:, :)
     105              :       REAL(dp), ALLOCATABLE                              :: values(:)
     106              :       REAL(dp), ALLOCATABLE                              :: cosp(:, :, :), rot(:, :, :), rot_g(:, :, :)
     107              :       REAL(dp), ALLOCATABLE :: hc(:, :, :), x(:, :, :), sx(:, :, :), c0(:, :, :), out(:, :, :), &
     108              :                                r(:, :, :), sinp(:, :, :), sc0(:, :, :), TARGET(:, :, :), os(:, :, :), &
     109              :                                buf1(:, :, :), buf2(:, :, :), buf3(:, :, :), buf4(:, :, :), buf_nk(:, :, :), &
     110              :                                buf1_ortho(:, :, :), buf2_ortho(:, :, :), sinp_b(:, :), cosp_b(:, :), tmp(:, :), tmp_ortho(:, :)
     111              :    END TYPE qs_ot_local_work_type
     112              : 
     113              : CONTAINS
     114              : 
     115              : ! **************************************************************************************************
     116              : !> \brief spectral norm of a dense anti-Hermitian rotation generator
     117              : !> \param rotation_generator anti-Hermitian generator
     118              : !> \return largest absolute eigenvalue of i times the generator
     119              : ! **************************************************************************************************
     120          180 :    FUNCTION qs_ot_antihermitian_spectral_norm(rotation_generator) RESULT(norm)
     121              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: rotation_generator
     122              :       REAL(KIND=dp)                                      :: norm
     123              : 
     124          180 :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :)     :: eigenvectors
     125              :       INTEGER                                            :: n
     126          180 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenvalues
     127              : 
     128          180 :       n = SIZE(rotation_generator, 1)
     129          180 :       CPASSERT(SIZE(rotation_generator, 2) == n)
     130          180 :       norm = 0.0_dp
     131          180 :       IF (n == 0) RETURN
     132         1080 :       ALLOCATE (eigenvectors(n, n), eigenvalues(n))
     133              :       CALL diag_complex(CMPLX(0.0_dp, 1.0_dp, KIND=dp)*rotation_generator, &
     134         8036 :                         eigenvectors, eigenvalues)
     135         1228 :       norm = MAXVAL(ABS(eigenvalues))
     136          180 :       DEALLOCATE (eigenvalues, eigenvectors)
     137              : 
     138          180 :    END FUNCTION qs_ot_antihermitian_spectral_norm
     139              : 
     140              : ! **************************************************************************************************
     141              : !> \brief chemical-potential response for one fixed-electron-number group
     142              : !> \param weighted_energy_response sum_i chi_i de_i over the perturbed local channels
     143              : !> \param local_curvature_sum local sum_i chi_i, used as a serial fallback
     144              : !> \param fixed_n_curvature_sum global sum_i chi_i for the complete fixed-N group
     145              : !> \return first-order chemical-potential shift
     146              : ! **************************************************************************************************
     147            0 :    PURE FUNCTION qs_ot_fixed_n_response_mu_shift( &
     148              :       weighted_energy_response, local_curvature_sum, fixed_n_curvature_sum) RESULT(mu_shift)
     149              :       REAL(KIND=dp), INTENT(IN)                          :: weighted_energy_response, &
     150              :                                                             local_curvature_sum, &
     151              :                                                             fixed_n_curvature_sum
     152              :       REAL(KIND=dp)                                      :: mu_shift
     153              : 
     154              :       REAL(KIND=dp)                                      :: denominator
     155              : 
     156            0 :       denominator = fixed_n_curvature_sum
     157            0 :       IF (ABS(denominator) <= EPSILON(denominator)) denominator = local_curvature_sum
     158            0 :       mu_shift = 0.0_dp
     159            0 :       IF (ABS(denominator) > EPSILON(denominator)) mu_shift = weighted_energy_response/denominator
     160              : 
     161            0 :    END FUNCTION qs_ot_fixed_n_response_mu_shift
     162              : 
     163              : ! **************************************************************************************************
     164              : !> \brief fixed-N Mermin gradient in auxiliary-energy coordinates
     165              : !> \param rayleigh_energy diagonal expectation values of the current Hamiltonian
     166              : !> \param energy_coordinate auxiliary band energies controlling the occupations
     167              : !> \param response_weight signed weighted occupation responses chi_i
     168              : !> \param fixed_n_weight_sum global sum_i chi_i for the fixed-N group
     169              : !> \param fixed_n_weighted_residual global sum_i chi_i (h_i-e_i)
     170              : !> \param gradient projected fixed-N gradient
     171              : ! **************************************************************************************************
     172         2096 :    PURE SUBROUTINE qs_ot_fixed_n_energy_gradient( &
     173         2096 :       rayleigh_energy, energy_coordinate, response_weight, fixed_n_weight_sum, &
     174         2096 :       fixed_n_weighted_residual, gradient)
     175              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: rayleigh_energy, energy_coordinate, &
     176              :                                                             response_weight
     177              :       REAL(KIND=dp), INTENT(IN)                          :: fixed_n_weight_sum, &
     178              :                                                             fixed_n_weighted_residual
     179              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: gradient
     180              : 
     181              :       REAL(KIND=dp)                                      :: fixed_n_mean
     182              : 
     183         2096 :       fixed_n_mean = 0.0_dp
     184         2096 :       IF (ABS(fixed_n_weight_sum) > EPSILON(fixed_n_weight_sum)) THEN
     185         2052 :          fixed_n_mean = fixed_n_weighted_residual/fixed_n_weight_sum
     186              :       END IF
     187              :       gradient(:) = response_weight(:)* &
     188        16360 :                     (fixed_n_mean - (rayleigh_energy(:) - energy_coordinate(:)))
     189              : 
     190         2096 :    END SUBROUTINE qs_ot_fixed_n_energy_gradient
     191              : 
     192              : ! **************************************************************************************************
     193              : !> \brief eliminate spin-resolved auxiliary energies while retaining every fixed-N constraint
     194              : !>
     195              : !>        Each energy variable belongs to one particle-number group. The diagonal occupation
     196              : !>        response is eliminated locally, while one coupling column per group is retained for the
     197              : !>        subsequent global chemical-potential projection. A single group reduces exactly to
     198              : !>        the single-group Schur complement A - R^T (D - chi chi^T/sum(chi)) R.
     199              : !> \param rotation_hessian finite orbital-rotation Hessian
     200              : !> \param rayleigh_response derivative of all spin-resolved Rayleigh energies
     201              : !> \param response_weight positive occupation-response weights
     202              : !> \param response_group fixed-N group of every energy variable
     203              : !> \param rotation_gradient orbital-rotation gradient
     204              : !> \param energy_gradient auxiliary-energy gradient
     205              : !> \param schur_block energy-eliminated rotation block
     206              : !> \param coupling_matrix retained chemical-potential coupling columns
     207              : !> \param schur_rhs energy-eliminated rotation right-hand side
     208              : ! **************************************************************************************************
     209          346 :    SUBROUTINE qs_ot_fixed_n_multigroup_schur_block( &
     210          692 :       rotation_hessian, rayleigh_response, response_weight, response_group, &
     211          692 :       rotation_gradient, energy_gradient, schur_block, coupling_matrix, schur_rhs)
     212              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: rotation_hessian, rayleigh_response
     213              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: response_weight
     214              :       INTEGER, DIMENSION(:), INTENT(IN)                  :: response_group
     215              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: rotation_gradient, energy_gradient
     216              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: schur_block, coupling_matrix
     217              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: schur_rhs
     218              : 
     219              :       INTEGER                                            :: group, i, nenergy, ngroups, nrotation
     220              :       REAL(KIND=dp)                                      :: weight
     221              : 
     222          346 :       nenergy = SIZE(response_weight)
     223          346 :       nrotation = SIZE(rotation_gradient)
     224          346 :       ngroups = SIZE(coupling_matrix, 2)
     225          346 :       CPASSERT(ngroups > 0)
     226         1038 :       CPASSERT(ALL(SHAPE(rotation_hessian) == [nrotation, nrotation]))
     227         1038 :       CPASSERT(ALL(SHAPE(rayleigh_response) == [nenergy, nrotation]))
     228          346 :       CPASSERT(SIZE(energy_gradient) == nenergy)
     229          346 :       CPASSERT(SIZE(response_group) == nenergy)
     230         2350 :       CPASSERT(ALL(response_group >= 1 .AND. response_group <= ngroups))
     231         1038 :       CPASSERT(ALL(SHAPE(schur_block) == [nrotation, nrotation]))
     232          346 :       CPASSERT(SIZE(coupling_matrix, 1) == nrotation)
     233          346 :       CPASSERT(SIZE(schur_rhs) == nrotation)
     234              : 
     235       530038 :       schur_block(:, :) = rotation_hessian(:, :)
     236        12674 :       coupling_matrix(:, :) = 0.0_dp
     237         2350 :       DO i = 1, nenergy
     238         2004 :          weight = response_weight(i)
     239              :          schur_block(:, :) = schur_block(:, :) - weight* &
     240              :                              SPREAD(rayleigh_response(i, :), DIM=2, NCOPIES=nrotation)* &
     241      5766876 :                              SPREAD(rayleigh_response(i, :), DIM=1, NCOPIES=nrotation)
     242         2004 :          group = response_group(i)
     243              :          coupling_matrix(:, group) = coupling_matrix(:, group) + &
     244        87910 :                                      weight*rayleigh_response(i, :)
     245              :       END DO
     246      1060076 :       schur_block(:, :) = 0.5_dp*(schur_block + TRANSPOSE(schur_block))
     247              :       schur_rhs(:) = rotation_gradient + &
     248        10568 :                      MATMUL(TRANSPOSE(rayleigh_response), energy_gradient)
     249              : 
     250          346 :    END SUBROUTINE qs_ot_fixed_n_multigroup_schur_block
     251              : 
     252              : ! **************************************************************************************************
     253              : !> \brief apply a positive spectral inverse of a real symmetric response matrix
     254              : !>
     255              : !>        The magnitude of every resolved eigenmode is retained, including modes with negative
     256              : !>        physical curvature. Replacing lambda by abs(lambda) gives a descent metric without the
     257              : !>        loss of response information caused by discarding the negative subspace. Unresolved
     258              : !>        null modes are projected out instead of being amplified by an artificial eigenvalue floor.
     259              : !> \param matrix real symmetric response matrix
     260              : !> \param rhs one or more right-hand sides
     261              : !> \param solution spectral-absolute inverse applied to rhs
     262              : !> \param valid whether finite input and output were obtained
     263              : !> \param relative_floor optional relative eigenvalue floor
     264              : ! **************************************************************************************************
     265          346 :    SUBROUTINE qs_ot_symmetric_abs_solve(matrix, rhs, solution, valid, relative_floor)
     266              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: matrix, rhs
     267              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: solution
     268              :       LOGICAL, INTENT(OUT)                               :: valid
     269              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: relative_floor
     270              : 
     271              :       INTEGER                                            :: i, n, nresolved
     272              :       REAL(KIND=dp)                                      :: eigenvalue_floor, relative_floor_eff, &
     273              :                                                             scale
     274          346 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenvalues
     275          346 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: eigenvectors, work
     276              : 
     277          346 :       n = SIZE(matrix, 1)
     278         1038 :       CPASSERT(ALL(SHAPE(matrix) == [n, n]))
     279          346 :       CPASSERT(SIZE(rhs, 1) == n)
     280         1038 :       CPASSERT(ALL(SHAPE(solution) == SHAPE(rhs)))
     281       552934 :       valid = ALL(matrix == matrix) .AND. ALL(rhs == rhs)
     282          346 :       IF (.NOT. valid) THEN
     283            0 :          solution(:, :) = 0.0_dp
     284              :          RETURN
     285              :       END IF
     286              : 
     287          346 :       relative_floor_eff = SQRT(EPSILON(1.0_dp))
     288          346 :       IF (PRESENT(relative_floor)) relative_floor_eff = MAX(relative_floor, relative_floor_eff)
     289         3114 :       ALLOCATE (eigenvalues(n), eigenvectors(n, n), work(n, SIZE(rhs, 2)))
     290       530038 :       eigenvectors(:, :) = 0.5_dp*(matrix + TRANSPOSE(matrix))
     291          346 :       CALL diamat_all(eigenvectors, eigenvalues)
     292        10222 :       scale = MAX(1.0_dp, MAXVAL(ABS(eigenvalues)))
     293          346 :       eigenvalue_floor = relative_floor_eff*scale
     294      2356498 :       work(:, :) = MATMUL(TRANSPOSE(eigenvectors), rhs)
     295          346 :       nresolved = 0
     296        10222 :       DO i = 1, n
     297        10222 :          IF (ABS(eigenvalues(i)) > eigenvalue_floor) THEN
     298        16899 :             work(i, :) = work(i, :)/ABS(eigenvalues(i))
     299         5289 :             nresolved = nresolved + 1
     300              :          ELSE
     301        14757 :             work(i, :) = 0.0_dp
     302              :          END IF
     303              :       END DO
     304      2369942 :       solution(:, :) = MATMUL(eigenvectors, work)
     305        22896 :       valid = nresolved > 0 .AND. ALL(solution == solution)
     306          634 :       IF (.NOT. valid) solution(:, :) = 0.0_dp
     307          346 :       DEALLOCATE (eigenvalues, eigenvectors, work)
     308              : 
     309          346 :    END SUBROUTINE qs_ot_symmetric_abs_solve
     310              : 
     311              : ! **************************************************************************************************
     312              : !> \brief update a baseline response direction in a small positive physical-response subspace
     313              : !>
     314              : !>        The first basis mode is the baseline response direction. With B0 the projected frozen-H
     315              : !>        Hessian and K the accepted physical correction, this routine solves
     316              : !>
     317              : !>          (B0 + K) c = g_Q.
     318              : !>
     319              : !>        The optional projected physical gradient supplies g_Q. Without it, g_Q=B0*e1, so c=e1
     320              : !>        exactly when K=0. An indefinite or unresolved total projected Hessian is rejected instead
     321              : !>        of turning its negative modes into an unrelated active direction.
     322              : !> \param reference_hessian projected frozen-H Hessian B0
     323              : !> \param response_correction projected physical response K
     324              : !> \param coefficients response coefficients c in the supplied basis
     325              : !> \param valid whether a finite, positive, sufficiently resolved solve was obtained
     326              : !> \param projected_gradient optional physical gradient projected onto the supplied basis
     327              : !> \param relative_floor optional relative positive-eigenvalue floor
     328              : ! **************************************************************************************************
     329          138 :    SUBROUTINE qs_ot_projected_response_update( &
     330          138 :       reference_hessian, response_correction, coefficients, valid, projected_gradient, relative_floor)
     331              : 
     332              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: reference_hessian, response_correction
     333              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: coefficients
     334              :       LOGICAL, INTENT(OUT)                               :: valid
     335              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN), OPTIONAL  :: projected_gradient
     336              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: relative_floor
     337              : 
     338              :       INTEGER                                            :: i, n
     339              :       REAL(KIND=dp)                                      :: eigenvalue_floor, relative_floor_eff, &
     340              :                                                             scale
     341          138 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenvalues, rhs, work
     342          138 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: eigenvectors
     343              : 
     344          138 :       n = SIZE(reference_hessian, 1)
     345          138 :       CPASSERT(n > 0)
     346          414 :       CPASSERT(ALL(SHAPE(reference_hessian) == [n, n]))
     347          414 :       CPASSERT(ALL(SHAPE(response_correction) == [n, n]))
     348          138 :       CPASSERT(SIZE(coefficients) == n)
     349          138 :       IF (PRESENT(projected_gradient)) THEN
     350          138 :          CPASSERT(SIZE(projected_gradient) == n)
     351              :       END IF
     352          414 :       coefficients(:) = 0.0_dp
     353              :       valid = ALL(reference_hessian == reference_hessian) .AND. &
     354         1932 :               ALL(response_correction == response_correction)
     355          138 :       IF (PRESENT(projected_gradient)) THEN
     356          414 :          valid = valid .AND. ALL(projected_gradient == projected_gradient)
     357              :       END IF
     358          138 :       IF (.NOT. valid) RETURN
     359              : 
     360          138 :       relative_floor_eff = 1.0E-4_dp
     361          138 :       IF (PRESENT(relative_floor)) relative_floor_eff = &
     362            0 :          MAX(relative_floor, SQRT(EPSILON(1.0_dp)))
     363         1104 :       ALLOCATE (eigenvalues(n), eigenvectors(n, n), rhs(n), work(n))
     364              :       eigenvectors(:, :) = 0.5_dp* &
     365              :                            (reference_hessian + response_correction + &
     366          966 :                             TRANSPOSE(reference_hessian + response_correction))
     367          138 :       CALL diamat_all(eigenvectors, eigenvalues)
     368         1242 :       scale = MAX(MAXVAL(ABS(eigenvalues)), MAXVAL(ABS(reference_hessian)))
     369          138 :       IF (scale <= TINY(scale)) THEN
     370            0 :          valid = .FALSE.
     371            0 :          coefficients(:) = 0.0_dp
     372            0 :          DEALLOCATE (eigenvalues, eigenvectors, rhs, work)
     373            0 :          RETURN
     374              :       END IF
     375          138 :       eigenvalue_floor = relative_floor_eff*scale
     376          406 :       valid = ALL(eigenvalues > eigenvalue_floor)
     377          138 :       IF (valid) THEN
     378          402 :          rhs(:) = reference_hessian(:, 1)
     379          402 :          IF (PRESENT(projected_gradient)) rhs(:) = projected_gradient
     380          134 :          work(:) = MATMUL(TRANSPOSE(eigenvectors), rhs)
     381          402 :          DO i = 1, n
     382          402 :             work(i) = work(i)/eigenvalues(i)
     383              :          END DO
     384         1742 :          coefficients(:) = MATMUL(eigenvectors, work)
     385              :          valid = ALL(coefficients == coefficients) .AND. &
     386          804 :                  ALL(ABS(coefficients) <= HUGE(1.0_dp))
     387              :       END IF
     388          146 :       IF (.NOT. valid) coefficients(:) = 0.0_dp
     389          138 :       DEALLOCATE (eigenvalues, eigenvectors, rhs, work)
     390              : 
     391          138 :    END SUBROUTINE qs_ot_projected_response_update
     392              : 
     393              : ! **************************************************************************************************
     394              : !> \brief form a projected self-adjoint Hxc response from distributed density-space overlaps
     395              : !> \param density_norm_sq <S,S>
     396              : !> \param response_work <S,Y>
     397              : !> \param density_overlap <S,B_r>
     398              : !> \param response_overlap <Y,B_r>
     399              : !> \param correction projected symmetric Hxc response <B_q,K*B_r>
     400              : !> \param valid whether finite nonzero secant data were available
     401              : !> \param secant_mode optional mode representing the accepted full density secant divided by its
     402              : !>        line-search position
     403              : !> \param secant_position signed line-search position of the accepted full density secant
     404              : ! **************************************************************************************************
     405          184 :    SUBROUTINE qs_ot_density_secant_projected_hessian( &
     406          184 :       density_norm_sq, response_work, density_overlap, response_overlap, correction, valid, &
     407              :       secant_mode, secant_position)
     408              : 
     409              :       REAL(KIND=dp), INTENT(IN)                          :: density_norm_sq, response_work
     410              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: density_overlap, response_overlap
     411              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: correction
     412              :       LOGICAL, INTENT(OUT)                               :: valid
     413              :       INTEGER, INTENT(IN), OPTIONAL                      :: secant_mode
     414              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: secant_position
     415              : 
     416              :       INTEGER                                            :: i, j, nmode
     417              :       REAL(KIND=dp)                                      :: inverse_density_norm
     418          368 :       REAL(KIND=dp), DIMENSION(SIZE(density_overlap))    :: projected_density_overlap, &
     419          184 :                                                             projected_response_overlap
     420              : 
     421          184 :       nmode = SIZE(density_overlap)
     422          184 :       CPASSERT(SIZE(response_overlap) == nmode)
     423          552 :       CPASSERT(ALL(SHAPE(correction) == [nmode, nmode]))
     424              : 
     425         1288 :       correction(:, :) = 0.0_dp
     426          184 :       valid = .FALSE.
     427          184 :       IF (nmode <= 0 .OR. density_norm_sq <= TINY(1.0_dp)) RETURN
     428          184 :       IF (density_norm_sq /= density_norm_sq .OR. response_work /= response_work) RETURN
     429          184 :       IF (ABS(density_norm_sq) > HUGE(1.0_dp) .OR. ABS(response_work) > HUGE(1.0_dp)) RETURN
     430         1104 :       IF (ANY(density_overlap /= density_overlap) .OR. ANY(response_overlap /= response_overlap)) RETURN
     431         1104 :       IF (ANY(ABS(density_overlap) > HUGE(1.0_dp)) .OR. &
     432              :           ANY(ABS(response_overlap) > HUGE(1.0_dp))) RETURN
     433          184 :       IF (PRESENT(secant_mode) .NEQV. PRESENT(secant_position)) RETURN
     434              : 
     435          552 :       projected_density_overlap(:) = density_overlap
     436          552 :       projected_response_overlap(:) = response_overlap
     437          184 :       IF (PRESENT(secant_mode)) THEN
     438          138 :          IF (secant_mode < 1 .OR. secant_mode > nmode) RETURN
     439              :          IF (secant_position /= secant_position .OR. &
     440          138 :              ABS(secant_position) > HUGE(1.0_dp) .OR. &
     441          138 :              ABS(secant_position) <= SQRT(EPSILON(1.0_dp))) RETURN
     442              :          ! The complete accepted density change can contain REF-orbital motion that is absent from
     443              :          ! a reduced rotation/occupation tangent.  S/alpha supplies its exact projected secant mode.
     444          138 :          projected_density_overlap(secant_mode) = density_norm_sq/secant_position
     445          138 :          projected_response_overlap(secant_mode) = response_work/secant_position
     446              :       END IF
     447              : 
     448          184 :       inverse_density_norm = 1.0_dp/density_norm_sq
     449          552 :       DO j = 1, nmode
     450         1288 :          DO i = 1, nmode
     451              :             correction(i, j) = inverse_density_norm* &
     452              :                                (projected_response_overlap(i)*projected_density_overlap(j) + &
     453              :                                 projected_density_overlap(i)*projected_response_overlap(j) - &
     454              :                                 response_work*inverse_density_norm* &
     455         1104 :                                 projected_density_overlap(i)*projected_density_overlap(j))
     456              :          END DO
     457              :       END DO
     458         2392 :       correction(:, :) = 0.5_dp*(correction + TRANSPOSE(correction))
     459         2576 :       valid = ALL(correction == correction) .AND. ALL(ABS(correction) <= HUGE(1.0_dp))
     460          184 :       IF (.NOT. valid) correction(:, :) = 0.0_dp
     461              : 
     462              :    END SUBROUTINE qs_ot_density_secant_projected_hessian
     463              : 
     464              : ! **************************************************************************************************
     465              : !> \brief finite-chart density tangent for coupled complex rotations and fixed-N occupations
     466              : !>
     467              : !>        The rotation contribution differentiates
     468              : !>
     469              : !>          exp(X) diag(w_k f) exp(X)^H
     470              : !>
     471              : !>        along an anti-Hermitian packed direction.  The supplied weighted occupation response is
     472              : !>        added in the same chart, and the result is returned in the current physical orbital basis.
     473              : !> \param rotation_generator current anti-Hermitian REF generator X
     474              : !> \param occupation current occupations f
     475              : !> \param kpoint_weight irreducible K-point weight w_k
     476              : !> \param rotation_step interleaved real/imaginary anti-Hermitian direction
     477              : !> \param weighted_occupation_step derivative of w_k*f, including the fixed-N mu response
     478              : !> \param density_tangent Hermitian tangent in the current physical orbital basis
     479              : !> \param difference_step optional finite-chart central-difference step
     480              : ! **************************************************************************************************
     481          496 :    SUBROUTINE qs_ot_density_tangent( &
     482          496 :       rotation_generator, occupation, kpoint_weight, rotation_step, &
     483          496 :       weighted_occupation_step, density_tangent, difference_step)
     484              : 
     485              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: rotation_generator
     486              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation
     487              :       REAL(KIND=dp), INTENT(IN)                          :: kpoint_weight
     488              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: rotation_step, weighted_occupation_step
     489              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: density_tangent
     490              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: difference_step
     491              : 
     492          496 :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: direction, generator_minus, &
     493          496 :          generator_plus, mode_ref, rotation, rotation_minus, rotation_plus, weighted_rotation
     494              :       INTEGER                                            :: i, j, n, nrotation, r
     495              :       REAL(KIND=dp)                                      :: step, step_scale
     496              :       TYPE(local_gemm_ctxt_type)                         :: gemm_ctx
     497          496 :       TYPE(qs_ot_rotation_work_type)                     :: rotation_work
     498              : 
     499          496 :       n = SIZE(rotation_generator, 1)
     500          496 :       nrotation = n*(n - 1)
     501            0 :       CPASSERT(n > 0)
     502         1488 :       CPASSERT(ALL(SHAPE(rotation_generator) == [n, n]))
     503          496 :       CPASSERT(SIZE(occupation) == n)
     504          496 :       CPASSERT(SIZE(rotation_step) == nrotation)
     505          496 :       CPASSERT(SIZE(weighted_occupation_step) == n)
     506         1488 :       CPASSERT(ALL(SHAPE(density_tangent) == [n, n]))
     507          496 :       CPASSERT(kpoint_weight > 0.0_dp)
     508              : 
     509              :       ALLOCATE (direction(n, n), generator_minus(n, n), generator_plus(n, n), &
     510              :                 mode_ref(n, n), rotation(n, n), rotation_minus(n, n), &
     511         8928 :                 rotation_plus(n, n), weighted_rotation(n, n))
     512          496 :       direction(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     513          496 :       r = 0
     514         3094 :       DO i = 1, n - 1
     515        12430 :          DO j = i + 1, n
     516         9336 :             r = r + 1
     517         9336 :             direction(i, j) = CMPLX(rotation_step(r), 0.0_dp, KIND=dp)
     518         9336 :             direction(j, i) = -direction(i, j)
     519         9336 :             r = r + 1
     520              :             direction(i, j) = direction(i, j) + &
     521         9336 :                               CMPLX(0.0_dp, rotation_step(r), KIND=dp)
     522              :             direction(j, i) = direction(j, i) + &
     523        11934 :                               CMPLX(0.0_dp, rotation_step(r), KIND=dp)
     524              :          END DO
     525              :       END DO
     526          496 :       CPASSERT(r == nrotation)
     527              : 
     528        25356 :       step_scale = MAX(1.0_dp, MAXVAL(ABS(direction)))
     529          496 :       step = 1.0E-5_dp/step_scale
     530          496 :       IF (PRESENT(difference_step)) step = difference_step/step_scale
     531          496 :       CPASSERT(step > EPSILON(step))
     532        25356 :       generator_plus(:, :) = rotation_generator + step*direction
     533        25356 :       generator_minus(:, :) = rotation_generator - step*direction
     534          496 :       CALL qs_ot_prepare_rotation_work(rotation_work, n)
     535          496 :       CALL gemm_ctx%create(LOCAL_GEMM_PU_HOST, timing=.FALSE.)
     536          496 :       CALL qs_ot_rotation_state_local(rotation_generator, rotation_work, gemm_ctx)
     537          496 :       CPASSERT(rotation_work%info == 0)
     538        25356 :       rotation(:, :) = rotation_work%u(:, :)
     539          496 :       CALL qs_ot_rotation_state_local(generator_plus, rotation_work, gemm_ctx)
     540          496 :       CPASSERT(rotation_work%info == 0)
     541        25356 :       rotation_plus(:, :) = rotation_work%u(:, :)
     542          496 :       CALL qs_ot_rotation_state_local(generator_minus, rotation_work, gemm_ctx)
     543          496 :       CPASSERT(rotation_work%info == 0)
     544        25356 :       rotation_minus(:, :) = rotation_work%u(:, :)
     545          496 :       CALL gemm_ctx%destroy()
     546              : 
     547        25356 :       weighted_rotation(:, :) = rotation_plus
     548         3590 :       DO j = 1, n
     549        25356 :          weighted_rotation(:, j) = kpoint_weight*occupation(j)*weighted_rotation(:, j)
     550              :       END DO
     551       195118 :       mode_ref(:, :) = MATMUL(weighted_rotation, CONJG(TRANSPOSE(rotation_plus)))
     552        25356 :       weighted_rotation(:, :) = rotation_minus
     553         3590 :       DO j = 1, n
     554        25356 :          weighted_rotation(:, j) = kpoint_weight*occupation(j)*weighted_rotation(:, j)
     555              :       END DO
     556              :       mode_ref(:, :) = (mode_ref - &
     557       221466 :                         MATMUL(weighted_rotation, CONJG(TRANSPOSE(rotation_minus))))/(2.0_dp*step)
     558              : 
     559        25356 :       weighted_rotation(:, :) = rotation
     560         3590 :       DO j = 1, n
     561        25356 :          weighted_rotation(:, j) = weighted_occupation_step(j)*weighted_rotation(:, j)
     562              :       END DO
     563       221962 :       mode_ref(:, :) = mode_ref + MATMUL(weighted_rotation, CONJG(TRANSPOSE(rotation)))
     564       585850 :       density_tangent(:, :) = MATMUL(CONJG(TRANSPOSE(rotation)), MATMUL(mode_ref, rotation))
     565        50216 :       density_tangent(:, :) = 0.5_dp*(density_tangent + CONJG(TRANSPOSE(density_tangent)))
     566              : 
     567            0 :       DEALLOCATE (direction, generator_minus, generator_plus, mode_ref, rotation, &
     568          496 :                   rotation_minus, rotation_plus, weighted_rotation)
     569              : 
     570          992 :    END SUBROUTINE qs_ot_density_tangent
     571              : 
     572              : ! **************************************************************************************************
     573              : !> \brief project a physical density/Hamiltonian secant between moving orbital subspaces
     574              : !>
     575              : !>        For separately S-orthonormal endpoint orbitals C0 and C1, O=C0^H*S*C1 retains the
     576              : !>        component of the accepted density step that leaves the old subspace.  Density modes are
     577              : !>        represented in the current C1 basis and already contain the irreducible K-point weight.
     578              : !> \param overlap_start_current cross overlap O
     579              : !> \param occupation_start occupations at the accepted start
     580              : !> \param occupation_current occupations at the accepted endpoint
     581              : !> \param hamiltonian_step_start C0^H*(H1-H0)*C0
     582              : !> \param hamiltonian_step_current C1^H*(H1-H0)*C1
     583              : !> \param density_modes current-orbital density tangents
     584              : !> \param kpoint_weight irreducible K-point weight
     585              : !> \param density_norm_sq contribution to <Delta P,Delta P>
     586              : !> \param response_work contribution to <Delta P,Delta H>
     587              : !> \param density_overlap contributions <Delta P,B_r>
     588              : !> \param response_overlap contributions <Delta H,B_r>
     589              : !> \param valid whether a finite nonzero secant was available
     590              : ! **************************************************************************************************
     591          284 :    SUBROUTINE qs_ot_density_secant_orbital_overlaps( &
     592          568 :       overlap_start_current, occupation_start, occupation_current, hamiltonian_step_start, &
     593          568 :       hamiltonian_step_current, density_modes, kpoint_weight, density_norm_sq, response_work, &
     594          284 :       density_overlap, response_overlap, valid)
     595              : 
     596              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: overlap_start_current
     597              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation_start, occupation_current
     598              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: hamiltonian_step_start, &
     599              :                                                             hamiltonian_step_current
     600              :       COMPLEX(KIND=dp), DIMENSION(:, :, :), INTENT(IN)   :: density_modes
     601              :       REAL(KIND=dp), INTENT(IN)                          :: kpoint_weight
     602              :       REAL(KIND=dp), INTENT(OUT)                         :: density_norm_sq, response_work
     603              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: density_overlap, response_overlap
     604              :       LOGICAL, INTENT(OUT)                               :: valid
     605              : 
     606          284 :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :)     :: cross_mode
     607              :       INTEGER                                            :: i, j, mode, n, nmode
     608              :       REAL(KIND=dp)                                      :: cross_density, density_scale
     609              : 
     610          284 :       n = SIZE(occupation_start)
     611          284 :       nmode = SIZE(density_modes, 3)
     612          284 :       CPASSERT(n > 0)
     613          284 :       CPASSERT(SIZE(occupation_current) == n)
     614          852 :       CPASSERT(ALL(SHAPE(overlap_start_current) == [n, n]))
     615          852 :       CPASSERT(ALL(SHAPE(hamiltonian_step_start) == [n, n]))
     616          852 :       CPASSERT(ALL(SHAPE(hamiltonian_step_current) == [n, n]))
     617          284 :       CPASSERT(SIZE(density_modes, 1) == n .AND. SIZE(density_modes, 2) == n)
     618          284 :       CPASSERT(SIZE(density_overlap) == nmode .AND. SIZE(response_overlap) == nmode)
     619              : 
     620          284 :       density_norm_sq = 0.0_dp
     621          284 :       response_work = 0.0_dp
     622          852 :       density_overlap(:) = 0.0_dp
     623          852 :       response_overlap(:) = 0.0_dp
     624          284 :       valid = .FALSE.
     625          292 :       IF (nmode <= 0 .OR. kpoint_weight <= TINY(1.0_dp)) RETURN
     626              : 
     627              :       cross_density = 0.0_dp
     628         2044 :       DO j = 1, n
     629        14364 :          DO i = 1, n
     630              :             cross_density = cross_density + occupation_start(i)*occupation_current(j)* &
     631        14080 :                             ABS(overlap_start_current(i, j))**2
     632              :          END DO
     633              :       END DO
     634              :       density_norm_sq = kpoint_weight* &
     635              :                         (SUM(occupation_start**2) + SUM(occupation_current**2) - &
     636         3804 :                          2.0_dp*cross_density)
     637              :       response_work = 0.0_dp
     638         2044 :       DO i = 1, n
     639              :          response_work = response_work + kpoint_weight* &
     640              :                          (occupation_current(i)*REAL(hamiltonian_step_current(i, i), KIND=dp) - &
     641         2044 :                           occupation_start(i)*REAL(hamiltonian_step_start(i, i), KIND=dp))
     642              :       END DO
     643              : 
     644         1136 :       ALLOCATE (cross_mode(n, n))
     645          852 :       DO mode = 1, nmode
     646              :          ! GCC 15 miscompiles the equivalent complex array expressions with -march=znver3.
     647              :          CALL gemm_square(overlap_start_current, "N", density_modes(:, :, mode), "N", &
     648          568 :                           overlap_start_current, "C", cross_mode)
     649         4088 :          DO i = 1, n
     650              :             density_overlap(mode) = density_overlap(mode) + &
     651              :                                     occupation_current(i)* &
     652              :                                     REAL(density_modes(i, i, mode), KIND=dp) - &
     653         4088 :                                     occupation_start(i)*REAL(cross_mode(i, i), KIND=dp)
     654              :          END DO
     655              :          response_overlap(mode) = &
     656              :             SUM(REAL(hamiltonian_step_current, KIND=dp)* &
     657              :                 REAL(density_modes(:, :, mode), KIND=dp) + &
     658        29012 :                 AIMAG(hamiltonian_step_current)*AIMAG(density_modes(:, :, mode)))
     659              :       END DO
     660          284 :       DEALLOCATE (cross_mode)
     661              : 
     662              :       density_scale = kpoint_weight* &
     663         3804 :                       MAX(MAXVAL(ABS(occupation_start)), MAXVAL(ABS(occupation_current)))
     664          284 :       IF (density_scale <= TINY(1.0_dp)) RETURN
     665          284 :       IF (density_norm_sq <= 64.0_dp*EPSILON(1.0_dp)*density_scale**2) RETURN
     666              :       valid = density_norm_sq == density_norm_sq .AND. response_work == response_work .AND. &
     667              :               ABS(density_norm_sq) <= HUGE(1.0_dp) .AND. ABS(response_work) <= HUGE(1.0_dp) .AND. &
     668              :               ALL(density_overlap == density_overlap) .AND. &
     669              :               ALL(response_overlap == response_overlap) .AND. &
     670              :               ALL(ABS(density_overlap) <= HUGE(1.0_dp)) .AND. &
     671         3312 :               ALL(ABS(response_overlap) <= HUGE(1.0_dp))
     672          276 :       IF (.NOT. valid) THEN
     673            0 :          density_norm_sq = 0.0_dp
     674            0 :          response_work = 0.0_dp
     675            0 :          density_overlap(:) = 0.0_dp
     676            0 :          response_overlap(:) = 0.0_dp
     677              :       END IF
     678              : 
     679            8 :    END SUBROUTINE qs_ot_density_secant_orbital_overlaps
     680              : 
     681              : ! **************************************************************************************************
     682              : !> \brief finite complex REF rotation Hessian and Rayleigh-energy response
     683              : !>
     684              : !>        The current projected Hamiltonian is pulled back through the finite rotation and then
     685              : !>        differentiated in the independent real-antisymmetric and imaginary-symmetric pair
     686              : !>        coordinates. This keeps the response consistent with the exponential chart used by REF
     687              : !>        OT instead of replacing it by an infinitesimal commutator away from the chart origin.
     688              : !> \param chc current projected Hermitian Hamiltonian U^H H_ref U
     689              : !> \param rotation_generator current anti-Hermitian REF generator
     690              : !> \param occupation fixed occupations attached to the rotated columns
     691              : !> \param kpoint_weight irreducible-k-point weight
     692              : !> \param rotation_gradient gradient in interleaved real/imaginary pair coordinates
     693              : !> \param rotation_hessian derivative of rotation_gradient in the same coordinates
     694              : !> \param rayleigh_response derivative of diag(U^H H_ref U) with respect to the pair coordinates
     695              : !> \param difference_step optional central finite-difference step for the Hessian action
     696              : ! **************************************************************************************************
     697          362 :    SUBROUTINE qs_ot_finite_rotation_response( &
     698          362 :       chc, rotation_generator, occupation, kpoint_weight, rotation_gradient, &
     699          362 :       rotation_hessian, rayleigh_response, difference_step)
     700              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: chc, rotation_generator
     701              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation
     702              :       REAL(KIND=dp), INTENT(IN)                          :: kpoint_weight
     703              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: rotation_gradient
     704              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: rotation_hessian, rayleigh_response
     705              :       REAL(KIND=dp), INTENT(IN), OPTIONAL                :: difference_step
     706              : 
     707              :       COMPLEX(KIND=dp)                                   :: perturbation
     708          362 :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :)     :: base_hamiltonian, generator_minus, &
     709          362 :                                                             generator_plus
     710              :       INTEGER                                            :: component, i, j, n, nrotation, r, s
     711              :       REAL(KIND=dp)                                      :: step
     712          362 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: occupation_scale, rayleigh, &
     713          362 :                                                             rayleigh_minus, rayleigh_plus
     714          362 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: gradient_imag, gradient_minus_imag, &
     715          362 :          gradient_minus_real, gradient_plus_imag, gradient_plus_real, gradient_real
     716              :       TYPE(local_gemm_ctxt_type)                         :: gemm_ctx
     717          362 :       TYPE(qs_ot_rotation_work_type)                     :: rotation_work
     718              : 
     719          362 :       n = SIZE(chc, 1)
     720          362 :       nrotation = n*(n - 1)
     721            0 :       CPASSERT(n > 0)
     722         1086 :       CPASSERT(ALL(SHAPE(chc) == [n, n]))
     723         1086 :       CPASSERT(ALL(SHAPE(rotation_generator) == [n, n]))
     724          362 :       CPASSERT(SIZE(occupation) == n)
     725          362 :       CPASSERT(SIZE(rotation_gradient) == nrotation)
     726         1086 :       CPASSERT(ALL(SHAPE(rotation_hessian) == [nrotation, nrotation]))
     727         1086 :       CPASSERT(ALL(SHAPE(rayleigh_response) == [n, nrotation]))
     728          362 :       CPASSERT(kpoint_weight > 0.0_dp)
     729              : 
     730          362 :       step = 1.0E-4_dp
     731          362 :       IF (PRESENT(difference_step)) step = difference_step
     732          362 :       CPASSERT(step > SQRT(EPSILON(step)))
     733              : 
     734              :       ALLOCATE (base_hamiltonian(n, n), generator_minus(n, n), generator_plus(n, n), &
     735              :                 occupation_scale(n), rayleigh(n), rayleigh_minus(n), &
     736              :                 rayleigh_plus(n), gradient_imag(n, n), gradient_minus_imag(n, n), &
     737              :                 gradient_minus_real(n, n), gradient_plus_imag(n, n), &
     738         9412 :                 gradient_plus_real(n, n), gradient_real(n, n))
     739              : 
     740          362 :       CALL qs_ot_prepare_rotation_work(rotation_work, n)
     741          362 :       CALL gemm_ctx%create(LOCAL_GEMM_PU_HOST, timing=.FALSE.)
     742          362 :       CALL qs_ot_rotation_state_local(rotation_generator, rotation_work, gemm_ctx)
     743          362 :       CPASSERT(rotation_work%info == 0)
     744              :       CALL gemm_ctx%gemm('N', 'C', n, n, n, (1.0_dp, 0.0_dp), chc, n, rotation_work%u, n, &
     745          362 :                          (0.0_dp, 0.0_dp), rotation_work%work, n)
     746              :       CALL gemm_ctx%gemm('N', 'N', n, n, n, (1.0_dp, 0.0_dp), rotation_work%u, n, rotation_work%work, n, &
     747          362 :                          (0.0_dp, 0.0_dp), base_hamiltonian, n)
     748         2370 :       occupation_scale(:) = 2.0_dp*kpoint_weight*occupation(:)
     749              :       CALL qs_ot_dense_rotation_gradient(base_hamiltonian, occupation_scale, &
     750          362 :                                          gradient_real, gradient_imag, rayleigh, rotation_work, gemm_ctx)
     751              : 
     752          362 :       r = 0
     753         2008 :       DO i = 1, n - 1
     754         7578 :          DO j = i + 1, n
     755         5570 :             r = r + 1
     756         5570 :             rotation_gradient(r) = gradient_real(i, j)
     757         5570 :             r = r + 1
     758         7216 :             rotation_gradient(r) = gradient_imag(i, j)
     759              :          END DO
     760              :       END DO
     761          362 :       CPASSERT(r == nrotation)
     762              : 
     763              :       s = 0
     764         2008 :       DO i = 1, n - 1
     765         7578 :          DO j = i + 1, n
     766        18356 :             DO component = 1, 2
     767        11140 :                perturbation = CMPLX(step, 0.0_dp, KIND=dp)
     768        11140 :                IF (component == 2) perturbation = CMPLX(0.0_dp, step, KIND=dp)
     769       815500 :                generator_plus(:, :) = rotation_generator(:, :)
     770       815500 :                generator_minus(:, :) = rotation_generator(:, :)
     771        11140 :                generator_plus(i, j) = generator_plus(i, j) + perturbation
     772        11140 :                generator_plus(j, i) = generator_plus(j, i) - CONJG(perturbation)
     773        11140 :                generator_minus(i, j) = generator_minus(i, j) - perturbation
     774        11140 :                generator_minus(j, i) = generator_minus(j, i) + CONJG(perturbation)
     775        11140 :                s = s + 1
     776        11140 :                CALL qs_ot_rotation_state_local(generator_plus, rotation_work, gemm_ctx)
     777        11140 :                CPASSERT(rotation_work%info == 0)
     778              :                CALL qs_ot_dense_rotation_gradient(base_hamiltonian, occupation_scale, &
     779        11140 :                                                   gradient_plus_real, gradient_plus_imag, rayleigh_plus, rotation_work, gemm_ctx)
     780        11140 :                CALL qs_ot_rotation_state_local(generator_minus, rotation_work, gemm_ctx)
     781        11140 :                CPASSERT(rotation_work%info == 0)
     782              :                CALL qs_ot_dense_rotation_gradient(base_hamiltonian, occupation_scale, &
     783        11140 :                                                   gradient_minus_real, gradient_minus_imag, rayleigh_minus, rotation_work, gemm_ctx)
     784              :                CALL qs_ot_pack_rotation_response( &
     785              :                   gradient_plus_real, gradient_plus_imag, gradient_minus_real, gradient_minus_imag, &
     786              :                   rayleigh_plus, rayleigh_minus, step, rotation_hessian(:, s), &
     787        16710 :                   rayleigh_response(:, s))
     788              :             END DO
     789              :          END DO
     790              :       END DO
     791          362 :       CPASSERT(s == nrotation)
     792      1289452 :       rotation_hessian(:, :) = 0.5_dp*(rotation_hessian + TRANSPOSE(rotation_hessian))
     793              : 
     794          362 :       CALL gemm_ctx%destroy()
     795            0 :       DEALLOCATE (base_hamiltonian, generator_minus, generator_plus, occupation_scale, &
     796            0 :                   rayleigh, rayleigh_minus, rayleigh_plus, gradient_imag, gradient_minus_imag, &
     797          362 :                   gradient_minus_real, gradient_plus_imag, gradient_plus_real, gradient_real)
     798              : 
     799          724 :    END SUBROUTINE qs_ot_finite_rotation_response
     800              : 
     801              : ! **************************************************************************************************
     802              : !> \brief pack one finite complex rotation response column
     803              : !> \param gradient_plus_real real gradient at the positive endpoint
     804              : !> \param gradient_plus_imag imaginary gradient at the positive endpoint
     805              : !> \param gradient_minus_real real gradient at the negative endpoint
     806              : !> \param gradient_minus_imag imaginary gradient at the negative endpoint
     807              : !> \param rayleigh_plus Rayleigh energies at the positive endpoint
     808              : !> \param rayleigh_minus Rayleigh energies at the negative endpoint
     809              : !> \param step central finite-difference step
     810              : !> \param hessian_column packed rotation-Hessian column
     811              : !> \param rayleigh_column packed Rayleigh-response column
     812              : ! **************************************************************************************************
     813        11140 :    SUBROUTINE qs_ot_pack_rotation_response( &
     814        11140 :       gradient_plus_real, gradient_plus_imag, gradient_minus_real, gradient_minus_imag, &
     815        11140 :       rayleigh_plus, rayleigh_minus, step, hessian_column, rayleigh_column)
     816              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: gradient_plus_real, gradient_plus_imag, &
     817              :                                                             gradient_minus_real, &
     818              :                                                             gradient_minus_imag
     819              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: rayleigh_plus, rayleigh_minus
     820              :       REAL(KIND=dp), INTENT(IN)                          :: step
     821              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: hessian_column, rayleigh_column
     822              : 
     823              :       INTEGER                                            :: i, j, n, r
     824              : 
     825        11140 :       n = SIZE(gradient_plus_real, 1)
     826        33420 :       CPASSERT(ALL(SHAPE(gradient_plus_real) == [n, n]))
     827        33420 :       CPASSERT(ALL(SHAPE(gradient_plus_imag) == [n, n]))
     828        33420 :       CPASSERT(ALL(SHAPE(gradient_minus_real) == [n, n]))
     829        33420 :       CPASSERT(ALL(SHAPE(gradient_minus_imag) == [n, n]))
     830        11140 :       CPASSERT(SIZE(hessian_column) == n*(n - 1))
     831        11140 :       CPASSERT(SIZE(rayleigh_column) == n)
     832              : 
     833        11140 :       r = 0
     834        85568 :       DO i = 1, n - 1
     835       402180 :          DO j = i + 1, n
     836       316612 :             r = r + 1
     837              :             hessian_column(r) = &
     838       316612 :                (gradient_plus_real(i, j) - gradient_minus_real(i, j))/(2.0_dp*step)
     839       316612 :             r = r + 1
     840              :             hessian_column(r) = &
     841       391040 :                (gradient_plus_imag(i, j) - gradient_minus_imag(i, j))/(2.0_dp*step)
     842              :          END DO
     843              :       END DO
     844        96708 :       rayleigh_column(:) = (rayleigh_plus(:) - rayleigh_minus(:))/(2.0_dp*step)
     845              : 
     846        11140 :    END SUBROUTINE qs_ot_pack_rotation_response
     847              : 
     848              : ! **************************************************************************************************
     849              : !> \brief Rotation gradient and projected-Hamiltonian diagonal from an already prepared rotation.
     850              : !> \param base_hamiltonian fixed Hamiltonian in the unrotated REF basis
     851              : !> \param occupation_scale twice the weighted occupations
     852              : !> \param gradient_real real-antisymmetric gradient component
     853              : !> \param gradient_imag imaginary-symmetric gradient component
     854              : !> \param rayleigh diagonal of the rotated Hamiltonian
     855              : !> \param w rotation state and private scratch
     856              : !> \param gemm_ctx caller-owned GEMM context
     857              : ! **************************************************************************************************
     858        22642 :    SUBROUTINE qs_ot_dense_rotation_gradient( &
     859        22642 :       base_hamiltonian, occupation_scale, gradient_real, gradient_imag, rayleigh, w, gemm_ctx)
     860              :       COMPLEX(KIND=dp), CONTIGUOUS, INTENT(IN)           :: base_hamiltonian(:, :)
     861              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation_scale
     862              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: gradient_real, gradient_imag
     863              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: rayleigh
     864              :       TYPE(qs_ot_rotation_work_type), INTENT(INOUT)      :: w
     865              :       TYPE(local_gemm_ctxt_type), INTENT(INOUT)          :: gemm_ctx
     866              : 
     867              :       INTEGER                                            :: i, j, n
     868              : 
     869        22642 :       n = w%solver%n
     870              :       CALL gemm_ctx%gemm('N', 'N', n, n, n, (1.0_dp, 0.0_dp), base_hamiltonian, n, w%u, n, &
     871        22642 :                          (0.0_dp, 0.0_dp), w%work, n)
     872              :       CALL gemm_ctx%gemm('C', 'N', n, n, n, (1.0_dp, 0.0_dp), w%u, n, w%work, n, &
     873        22642 :                          (0.0_dp, 0.0_dp), w%inner, n)
     874       195786 :       DO i = 1, n
     875       195786 :          rayleigh(i) = REAL(w%inner(i, i), KIND=dp)
     876              :       END DO
     877              : 
     878              :       ! Reuse H*U for the covector instead of multiplying a second time.
     879       195786 :       DO j = 1, n
     880      1646518 :          w%work(:, j) = occupation_scale(j)*w%work(:, j)
     881              :       END DO
     882        22642 :       CALL qs_ot_rotation_pullback_local(w, gemm_ctx, gradient_real, gradient_imag)
     883              : 
     884        22642 :    END SUBROUTINE qs_ot_dense_rotation_gradient
     885              : 
     886              : ! **************************************************************************************************
     887              : !> \brief Pull back dE/dU through exp(A) and project onto anti-Hermitian generator coordinates.
     888              : !> \param w prepared rotation spectrum; work contains dE/dU on entry and is overwritten
     889              : !> \param gemm_ctx caller-owned GEMM context
     890              : !> \param gradient_real real-antisymmetric gradient component
     891              : !> \param gradient_imag imaginary-symmetric gradient component
     892              : ! **************************************************************************************************
     893        23163 :    SUBROUTINE qs_ot_rotation_pullback_local(w, gemm_ctx, gradient_real, gradient_imag)
     894              :       TYPE(qs_ot_rotation_work_type), INTENT(INOUT)      :: w
     895              :       TYPE(local_gemm_ctxt_type), INTENT(INOUT)          :: gemm_ctx
     896              :       REAL(KIND=dp), INTENT(OUT)                         :: gradient_real(:, :), gradient_imag(:, :)
     897              : 
     898              :       INTEGER                                            :: i, j, n
     899              : 
     900        23163 :       n = w%solver%n
     901              :       CALL gemm_ctx%gemm('N', 'N', n, n, n, (1.0_dp, 0.0_dp), w%work, n, w%vectors, n, &
     902        23163 :                          (0.0_dp, 0.0_dp), w%inner, n)
     903              :       CALL gemm_ctx%gemm('C', 'N', n, n, n, (1.0_dp, 0.0_dp), w%vectors, n, w%inner, n, &
     904        23163 :                          (0.0_dp, 0.0_dp), w%work, n)
     905       201605 :       DO j = 1, n
     906      1726581 :          DO i = 1, n
     907              :             w%work(i, j) = w%work(i, j)* &
     908      1703418 :                            CONJG(qs_ot_complex_exp_frechet_kernel(w%solver%evals(i), w%solver%evals(j)))
     909              :          END DO
     910              :       END DO
     911              :       CALL gemm_ctx%gemm('N', 'C', n, n, n, (1.0_dp, 0.0_dp), w%work, n, w%vectors, n, &
     912        23163 :                          (0.0_dp, 0.0_dp), w%inner, n)
     913              :       CALL gemm_ctx%gemm('N', 'N', n, n, n, (1.0_dp, 0.0_dp), w%vectors, n, w%inner, n, &
     914        23163 :                          (0.0_dp, 0.0_dp), w%work, n)
     915       201605 :       DO j = 1, n
     916      1726581 :          DO i = 1, n
     917      1524976 :             gradient_real(i, j) = REAL(w%work(i, j), KIND=dp) - REAL(w%work(j, i), KIND=dp)
     918      1703418 :             gradient_imag(i, j) = AIMAG(w%work(i, j)) + AIMAG(w%work(j, i))
     919              :          END DO
     920              :       END DO
     921        23163 :    END SUBROUTINE qs_ot_rotation_pullback_local
     922              : 
     923              : ! **************************************************************************************************
     924              : !> \brief Prepare private rotation arrays and LAPACK scratch once per matrix order.
     925              : !> \param w caller-owned rotation workspace
     926              : !> \param n rotation dimension
     927              : ! **************************************************************************************************
     928         2330 :    SUBROUTINE qs_ot_prepare_rotation_work(w, n)
     929              :       TYPE(qs_ot_rotation_work_type), INTENT(INOUT)      :: w
     930              :       INTEGER, INTENT(IN)                                :: n
     931              : 
     932         2330 :       CPASSERT(n > 0)
     933         2330 :       IF (ALLOCATED(w%vectors)) THEN
     934          394 :          IF (SIZE(w%vectors, 1) == n) RETURN
     935            0 :          DEALLOCATE (w%vectors, w%u, w%work, w%inner)
     936              :       END IF
     937        19360 :       ALLOCATE (w%vectors(n, n), w%u(n, n), w%work(n, n), w%inner(n, n))
     938         1936 :       CALL cp_cfm_local_workspace_prepare(w%solver, n, w%vectors)
     939              :    END SUBROUTINE qs_ot_prepare_rotation_work
     940              : 
     941              : ! **************************************************************************************************
     942              : !> \brief Evaluate exp(A) and retain its spectrum without allocating or communicating.
     943              : !> \param rotation_generator anti-Hermitian generator
     944              : !> \param w prepared workspace; u receives exp(A), info reports LAPACK failure
     945              : !> \param gemm_ctx caller-owned GEMM context
     946              : ! **************************************************************************************************
     947        25602 :    SUBROUTINE qs_ot_rotation_state_local(rotation_generator, w, gemm_ctx)
     948              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: rotation_generator
     949              :       TYPE(qs_ot_rotation_work_type), INTENT(INOUT)      :: w
     950              :       TYPE(local_gemm_ctxt_type), INTENT(INOUT)          :: gemm_ctx
     951              : 
     952              :       INTEGER                                            :: j, n
     953              : 
     954        25602 :       n = w%solver%n
     955      1951530 :       w%vectors(:, :) = CMPLX(0.0_dp, 1.0_dp, KIND=dp)*rotation_generator
     956        25602 :       CALL cp_cfm_heevd_local(w%vectors, info=w%info, ws=w%solver)
     957        25602 :       IF (w%info /= 0) RETURN
     958       222992 :       DO j = 1, n
     959      1951530 :          w%work(:, j) = EXP(CMPLX(0.0_dp, -w%solver%evals(j), KIND=dp))*w%vectors(:, j)
     960              :       END DO
     961              :       CALL gemm_ctx%gemm('N', 'C', n, n, n, (1.0_dp, 0.0_dp), w%work, n, w%vectors, n, &
     962        25602 :                          (0.0_dp, 0.0_dp), w%u, n)
     963              :    END SUBROUTINE qs_ot_rotation_state_local
     964              : 
     965              : ! **************************************************************************************************
     966              : !> \brief Frechet divided-difference kernel for exp(-i*evals)
     967              : !> \param e1 ...
     968              : !> \param e2 ...
     969              : !> \return ...
     970              : ! **************************************************************************************************
     971      1569500 :    PURE FUNCTION qs_ot_complex_exp_frechet_kernel(e1, e2) RESULT(kernel)
     972              :       REAL(KIND=dp), INTENT(IN)                          :: e1, e2
     973              :       COMPLEX(KIND=dp)                                   :: kernel
     974              : 
     975              :       COMPLEX(KIND=dp)                                   :: l1, l2, x
     976              :       INTEGER                                            :: i
     977              : 
     978      1569500 :       l1 = (0.0_dp, -1.0_dp)*e1
     979      1569500 :       l2 = (0.0_dp, -1.0_dp)*e2
     980      1569500 :       IF (ABS(l1 - l2) > 0.5_dp) THEN
     981        12696 :          kernel = (EXP(l1) - EXP(l2))/(l1 - l2)
     982              :       ELSE
     983              :          x = 1.0_dp
     984              :          kernel = 0.0_dp
     985     26465668 :          DO i = 1, 16
     986     24908864 :             kernel = kernel + x
     987     26465668 :             x = x*(l1 - l2)/REAL(i + 1, KIND=dp)
     988              :          END DO
     989      1556804 :          kernel = kernel*EXP(l2)
     990              :       END IF
     991              : 
     992      1569500 :    END FUNCTION qs_ot_complex_exp_frechet_kernel
     993              : 
     994              : ! **************************************************************************************************
     995              : !> \brief apply the complex exponential Frechet kernel to sparse DBCSR Re/Im matrices
     996              : !> \param evals generator eigenvalues
     997              : !> \param inner_deriv_re real part of the matrix in the generator eigenbasis
     998              : !> \param inner_deriv_im imaginary part of the matrix in the generator eigenbasis
     999              : !> \param outer_deriv_re real part of the mapped matrix
    1000              : !> \param outer_deriv_im imaginary part of the mapped matrix
    1001              : !> \param adjoint use the adjoint Frechet kernel for gradients
    1002              : ! **************************************************************************************************
    1003         1098 :    SUBROUTINE qs_ot_apply_complex_frechet_dbcsr(evals, inner_deriv_re, inner_deriv_im, &
    1004              :                                                 outer_deriv_re, outer_deriv_im, adjoint)
    1005              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: evals
    1006              :       TYPE(dbcsr_type)                                   :: inner_deriv_re, inner_deriv_im, &
    1007              :                                                             outer_deriv_re, outer_deriv_im
    1008              :       LOGICAL, INTENT(IN), OPTIONAL                      :: adjoint
    1009              : 
    1010              :       COMPLEX(KIND=dp)                                   :: cval_in, kernel
    1011              :       INTEGER                                            :: col, i, j, max_blocks, nblocks, row
    1012         1098 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: cols, rows
    1013         1098 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk_offset, col_blk_size, &
    1014         1098 :                                                             row_blk_offset, row_blk_size
    1015              :       LOGICAL                                            :: found_im, found_out_im, found_out_re, &
    1016              :                                                             found_re, use_adjoint
    1017         1098 :       REAL(dp), DIMENSION(:, :), POINTER                 :: block_in_im, block_in_re, block_out_im, &
    1018         1098 :                                                             block_out_re
    1019              :       REAL(KIND=dp)                                      :: e1, e2, im_part, re_part
    1020              :       TYPE(dbcsr_distribution_type)                      :: dist
    1021              :       TYPE(dbcsr_iterator_type)                          :: iter
    1022              : 
    1023         1098 :       use_adjoint = .FALSE.
    1024         1096 :       IF (PRESENT(adjoint)) use_adjoint = adjoint
    1025              : 
    1026              :       ! Re/Im parts can have different sparse block patterns. Build their union
    1027              :       ! explicitly so a missing partner block is interpreted as zero.
    1028         1098 :       max_blocks = 0
    1029         1098 :       CALL dbcsr_iterator_start(iter, inner_deriv_re)
    1030         1650 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1031          552 :          CALL dbcsr_iterator_next_block(iter, row, col)
    1032          552 :          max_blocks = max_blocks + 1
    1033              :       END DO
    1034         1098 :       CALL dbcsr_iterator_stop(iter)
    1035         1098 :       CALL dbcsr_iterator_start(iter, inner_deriv_im)
    1036         1650 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1037          552 :          CALL dbcsr_iterator_next_block(iter, row, col)
    1038          552 :          max_blocks = max_blocks + 1
    1039              :       END DO
    1040         1098 :       CALL dbcsr_iterator_stop(iter)
    1041         4392 :       ALLOCATE (rows(MAX(max_blocks, 1)), cols(MAX(max_blocks, 1)))
    1042         1098 :       nblocks = 0
    1043              : 
    1044         1098 :       CALL dbcsr_iterator_start(iter, inner_deriv_re)
    1045         1650 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1046          552 :          CALL dbcsr_iterator_next_block(iter, row, col)
    1047          552 :          CALL append_union_block(row, col)
    1048              :       END DO
    1049         1098 :       CALL dbcsr_iterator_stop(iter)
    1050         1098 :       CALL dbcsr_iterator_start(iter, inner_deriv_im)
    1051         1650 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1052          552 :          CALL dbcsr_iterator_next_block(iter, row, col)
    1053          552 :          CALL append_union_block(row, col)
    1054              :       END DO
    1055         1098 :       CALL dbcsr_iterator_stop(iter)
    1056              : 
    1057              :       CALL dbcsr_get_info(inner_deriv_re, distribution=dist, row_blk_size=row_blk_size, &
    1058         1098 :                           col_blk_size=col_blk_size)
    1059              :       CALL dbcsr_create(outer_deriv_re, "outer_deriv_re", dist, dbcsr_type_no_symmetry, &
    1060         1098 :                         row_blk_size, col_blk_size)
    1061              :       CALL dbcsr_create(outer_deriv_im, "outer_deriv_im", dist, dbcsr_type_no_symmetry, &
    1062         1098 :                         row_blk_size, col_blk_size)
    1063         1098 :       IF (nblocks > 0) THEN
    1064          552 :          CALL dbcsr_reserve_blocks(outer_deriv_re, rows=rows(1:nblocks), cols=cols(1:nblocks))
    1065          552 :          CALL dbcsr_reserve_blocks(outer_deriv_im, rows=rows(1:nblocks), cols=cols(1:nblocks))
    1066              :       END IF
    1067         1098 :       CALL dbcsr_finalize(outer_deriv_re)
    1068         1098 :       CALL dbcsr_finalize(outer_deriv_im)
    1069         1098 :       CALL dbcsr_set(outer_deriv_re, 0.0_dp)
    1070         1098 :       CALL dbcsr_set(outer_deriv_im, 0.0_dp)
    1071              : 
    1072              :       CALL dbcsr_get_info(outer_deriv_re, row_blk_offset=row_blk_offset, &
    1073         1098 :                           col_blk_offset=col_blk_offset)
    1074         1098 :       CALL dbcsr_iterator_start(iter, outer_deriv_re)
    1075         1652 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1076          554 :          CALL dbcsr_iterator_next_block(iter, row, col)
    1077          554 :          CALL dbcsr_get_block_p(inner_deriv_re, row, col, block_in_re, found_re)
    1078          554 :          CALL dbcsr_get_block_p(inner_deriv_im, row, col, block_in_im, found_im)
    1079          554 :          CALL dbcsr_get_block_p(outer_deriv_re, row, col, block_out_re, found_out_re)
    1080          554 :          CALL dbcsr_get_block_p(outer_deriv_im, row, col, block_out_im, found_out_im)
    1081          554 :          CPASSERT(found_out_re .AND. found_out_im)
    1082              : 
    1083         5558 :          DO i = 1, SIZE(block_out_re, 1)
    1084        48980 :          DO j = 1, SIZE(block_out_re, 2)
    1085        44520 :             e1 = evals(row_blk_offset(row) + i - 1)
    1086        44520 :             e2 = evals(col_blk_offset(col) + j - 1)
    1087        44520 :             re_part = 0.0_dp
    1088        44520 :             im_part = 0.0_dp
    1089        44520 :             IF (found_re) re_part = block_in_re(i, j)
    1090        44520 :             IF (found_im) im_part = block_in_im(i, j)
    1091        44520 :             cval_in = CMPLX(re_part, im_part, dp)
    1092        44520 :             kernel = qs_ot_complex_exp_frechet_kernel(e1, e2)
    1093        44520 :             IF (use_adjoint) kernel = CONJG(kernel)
    1094        44520 :             cval_in = cval_in*kernel
    1095        44520 :             block_out_re(i, j) = REAL(cval_in, KIND=dp)
    1096        48426 :             block_out_im(i, j) = AIMAG(cval_in)
    1097              :          END DO
    1098              :          END DO
    1099              :       END DO
    1100         1098 :       CALL dbcsr_iterator_stop(iter)
    1101         8782 :       DEALLOCATE (rows, cols)
    1102              : 
    1103              :    CONTAINS
    1104              : 
    1105              : ! **************************************************************************************************
    1106              : !> \brief append a block coordinate unless it is already present
    1107              : !> \param row_new block-row index
    1108              : !> \param col_new block-column index
    1109              : ! **************************************************************************************************
    1110         1104 :       SUBROUTINE append_union_block(row_new, col_new)
    1111              :       INTEGER, INTENT(IN)                                :: row_new, col_new
    1112              : 
    1113              :       INTEGER                                            :: iblock
    1114              : 
    1115         1106 :          DO iblock = 1, nblocks
    1116         1106 :             IF (rows(iblock) == row_new .AND. cols(iblock) == col_new) RETURN
    1117              :          END DO
    1118          554 :          nblocks = nblocks + 1
    1119          554 :          rows(nblocks) = row_new
    1120          554 :          cols(nblocks) = col_new
    1121              :       END SUBROUTINE append_union_block
    1122              : 
    1123              :    END SUBROUTINE qs_ot_apply_complex_frechet_dbcsr
    1124              : 
    1125              : ! **************************************************************************************************
    1126              : !> \brief update complex REF k-point orbitals and their S(k)C(k) images
    1127              : !> \param matrix_c ...
    1128              : !> \param matrix_c_im ...
    1129              : !> \param matrix_s ...
    1130              : !> \param matrix_s_im ...
    1131              : !> \param qs_ot_env ...
    1132              : !> \param qs_ot_env1 environment carrying the shared minimizer state
    1133              : ! **************************************************************************************************
    1134         1842 :    SUBROUTINE qs_ot_get_orbitals_ref_complex(matrix_c, matrix_c_im, matrix_s, matrix_s_im, &
    1135              :                                              qs_ot_env, qs_ot_env1)
    1136              : 
    1137              :       TYPE(dbcsr_type), POINTER                          :: matrix_c, matrix_c_im, matrix_s, &
    1138              :                                                             matrix_s_im
    1139              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1140              :       TYPE(qs_ot_type), OPTIONAL                         :: qs_ot_env1
    1141              : 
    1142              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_get_orbitals_ref_complex'
    1143              : 
    1144              :       INTEGER                                            :: handle, i, k, n
    1145              :       LOGICAL                                            :: update
    1146         1842 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenvalues, inverse_sqrt
    1147              :       TYPE(dbcsr_type)                                   :: rotated_im, rotated_re, rotation_tmp
    1148              :       TYPE(dbcsr_type), POINTER                          :: c_im, c_re, f_im, f_re, p_im, p_re, &
    1149              :                                                             sc_im, sc_re, tmp_kk, tmp_nk, v_im, &
    1150              :                                                             v_re, w_im, w_re
    1151              : 
    1152         1842 :       CALL timeset(routineN, handle)
    1153              : 
    1154         1842 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    1155         1842 :       CPASSERT(ASSOCIATED(matrix_s))
    1156         1842 :       CPASSERT(ASSOCIATED(matrix_s_im))
    1157              : 
    1158         1842 :       c_re => qs_ot_env%matrix_x
    1159         1842 :       c_im => qs_ot_env%matrix_x_im
    1160         1842 :       f_re => qs_ot_env%matrix_ref_inv_sqrt
    1161         1842 :       f_im => qs_ot_env%matrix_ref_inv_sqrt_im
    1162         1842 :       sc_re => qs_ot_env%matrix_sx
    1163         1842 :       sc_im => qs_ot_env%matrix_sx_im
    1164         1842 :       p_re => qs_ot_env%buf1_k_k_sym
    1165         1842 :       p_im => qs_ot_env%buf2_k_k_sym
    1166         1842 :       v_re => qs_ot_env%buf3_k_k_sym
    1167         1842 :       v_im => qs_ot_env%buf4_k_k_sym
    1168         1842 :       w_re => qs_ot_env%buf1_k_k_nosym
    1169         1842 :       w_im => qs_ot_env%buf2_k_k_nosym
    1170         1842 :       tmp_kk => qs_ot_env%buf3_k_k_nosym
    1171         1842 :       tmp_nk => qs_ot_env%buf1_n_k
    1172              : 
    1173         1842 :       CALL dbcsr_get_info(c_re, nfullrows_total=n, nfullcols_total=k)
    1174         1842 :       IF (PRESENT(qs_ot_env1)) THEN
    1175         1724 :          update = qs_ot_ref_update(qs_ot_env1)
    1176              :       ELSE
    1177          118 :          update = qs_ot_ref_update(qs_ot_env)
    1178              :       END IF
    1179              : 
    1180              :       ! SC = (S_re + i*S_im) * (C_re + i*C_im)
    1181         1842 :       CALL qs_ot_complex_multiply('N', 'N', matrix_s, matrix_s_im, c_re, c_im, sc_re, sc_im, tmp_nk)
    1182              : 
    1183              :       ! P = C^H*S*C.  Its imaginary component is real antisymmetric.
    1184         1842 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, c_re, sc_re, 0.0_dp, p_re)
    1185         1842 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, c_im, sc_im, 0.0_dp, tmp_kk)
    1186         1842 :       CALL dbcsr_add(p_re, tmp_kk, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1187              : 
    1188         1842 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, c_re, sc_im, 0.0_dp, p_im)
    1189         1842 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, c_im, sc_re, 0.0_dp, tmp_kk)
    1190         1842 :       CALL dbcsr_add(p_im, tmp_kk, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1191              : 
    1192         7368 :       ALLOCATE (eigenvalues(k), inverse_sqrt(k))
    1193              :       CALL cp_dbcsr_heevd(matrix_re=p_re, matrix_im=p_im, &
    1194              :                           eigenvectors_re=v_re, eigenvectors_im=v_im, &
    1195              :                           eigenvalues=eigenvalues, para_env=qs_ot_env%para_env, &
    1196         1842 :                           blacs_env=qs_ot_env%blacs_env)
    1197        10858 :       DO i = 1, k
    1198         9016 :          IF (eigenvalues(i) <= EPSILON(1.0_dp)) THEN
    1199            0 :             CPABORT("Complex REF overlap is not positive definite")
    1200              :          END IF
    1201        10858 :          inverse_sqrt(i) = 1.0_dp/SQRT(eigenvalues(i))
    1202              :       END DO
    1203              : 
    1204              :       ! P^(-1/2) = V*diag(lambda^(-1/2))*V^H.
    1205         1842 :       CALL dbcsr_copy(w_re, v_re)
    1206         1842 :       CALL dbcsr_copy(w_im, v_im)
    1207         1842 :       CALL dbcsr_scale_by_vector(w_re, alpha=inverse_sqrt, side='right')
    1208         1842 :       CALL dbcsr_scale_by_vector(w_im, alpha=inverse_sqrt, side='right')
    1209              : 
    1210         1842 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, w_re, v_re, 0.0_dp, p_re)
    1211         1842 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, w_im, v_im, 0.0_dp, tmp_kk)
    1212         1842 :       CALL dbcsr_add(p_re, tmp_kk, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1213              : 
    1214         1842 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, w_im, v_re, 0.0_dp, p_im)
    1215         1842 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, w_re, v_im, 0.0_dp, tmp_kk)
    1216         1842 :       CALL dbcsr_add(p_im, tmp_kk, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1217              : 
    1218         1842 :       CALL dbcsr_copy(f_re, p_re)
    1219         1842 :       CALL dbcsr_copy(f_im, p_im)
    1220              : 
    1221              :       ! Return the physical, orthonormal orbitals without changing a rejected
    1222              :       ! line-search coordinate.
    1223         1842 :       CALL qs_ot_complex_multiply('N', 'N', c_re, c_im, p_re, p_im, matrix_c, matrix_c_im, tmp_nk)
    1224              : 
    1225         1842 :       IF (update) THEN
    1226          610 :          CALL dbcsr_copy(c_re, matrix_c)
    1227          610 :          CALL dbcsr_copy(c_im, matrix_c_im)
    1228              : 
    1229          610 :          CALL dbcsr_set(f_re, 0.0_dp)
    1230          610 :          CALL dbcsr_add_on_diag(f_re, alpha=1.0_dp)
    1231          610 :          CALL dbcsr_set(f_im, 0.0_dp)
    1232              : 
    1233          610 :          CALL qs_ot_complex_multiply('N', 'N', matrix_s, matrix_s_im, c_re, c_im, sc_re, sc_im, tmp_nk)
    1234              :       END IF
    1235              : 
    1236         1842 :       IF (qs_ot_env%settings%do_rotation) THEN
    1237         1272 :          CALL qs_ot_generate_rotation_complex(qs_ot_env)
    1238              : 
    1239         1272 :          CALL dbcsr_copy(rotated_re, matrix_c, name="rotated_re")
    1240         1272 :          CALL dbcsr_copy(rotated_im, matrix_c_im, name="rotated_im")
    1241         1272 :          CALL dbcsr_copy(rotation_tmp, matrix_c, name="rotation_tmp")
    1242              : 
    1243              :          ! C_out = Q*U for complex Q and unitary U.
    1244              :          CALL qs_ot_complex_multiply('N', 'N', matrix_c, matrix_c_im, qs_ot_env%rot_mat_u, &
    1245         1272 :                                      qs_ot_env%rot_mat_u_im, rotated_re, rotated_im, rotation_tmp)
    1246              : 
    1247         1272 :          CALL dbcsr_copy(matrix_c, rotated_re)
    1248         1272 :          CALL dbcsr_copy(matrix_c_im, rotated_im)
    1249         1272 :          CALL dbcsr_release(rotated_re)
    1250         1272 :          CALL dbcsr_release(rotated_im)
    1251         1272 :          CALL dbcsr_release(rotation_tmp)
    1252              :       END IF
    1253              : 
    1254         1842 :       DEALLOCATE (eigenvalues, inverse_sqrt)
    1255              : 
    1256         1842 :       CALL timestop(handle)
    1257         3684 :    END SUBROUTINE qs_ot_get_orbitals_ref_complex
    1258              : 
    1259              : ! **************************************************************************************************
    1260              : !> \brief complex k-point REF derivative dE/dX from H(k)C(k), S(k)C(k), and C(k)
    1261              : !> \param matrix_hc ...
    1262              : !> \param matrix_hc_im ...
    1263              : !> \param qs_ot_env ...
    1264              : ! **************************************************************************************************
    1265         2168 :    SUBROUTINE qs_ot_get_derivative_ref_complex(matrix_hc, matrix_hc_im, qs_ot_env)
    1266              :       TYPE(dbcsr_type), POINTER                          :: matrix_hc, matrix_hc_im
    1267              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1268              : 
    1269              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_get_derivative_ref_complex'
    1270              : 
    1271              :       INTEGER                                            :: handle, k, n
    1272              :       TYPE(dbcsr_type)                                   :: tmp_nk
    1273              :       TYPE(dbcsr_type), POINTER                          :: b_im, b_re, f_im, f_re, g_im, g_re, &
    1274              :                                                             hc_im, hc_re, hc_work_im, hc_work_re, &
    1275              :                                                             q_im, q_re, sc_im, sc_re, tmp_kk
    1276              :       TYPE(dbcsr_type), TARGET                           :: hc_rot_im, hc_rot_re
    1277              : 
    1278         1084 :       CALL timeset(routineN, handle)
    1279              : 
    1280         1084 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    1281         1084 :       CPASSERT(ASSOCIATED(matrix_hc))
    1282         1084 :       CPASSERT(ASSOCIATED(matrix_hc_im))
    1283              : 
    1284         1084 :       f_re => qs_ot_env%matrix_ref_inv_sqrt
    1285         1084 :       f_im => qs_ot_env%matrix_ref_inv_sqrt_im
    1286         1084 :       sc_re => qs_ot_env%matrix_sx
    1287         1084 :       sc_im => qs_ot_env%matrix_sx_im
    1288         1084 :       hc_re => matrix_hc
    1289         1084 :       hc_im => matrix_hc_im
    1290         1084 :       hc_work_re => hc_re
    1291         1084 :       hc_work_im => hc_im
    1292         1084 :       g_re => qs_ot_env%matrix_gx
    1293         1084 :       g_im => qs_ot_env%matrix_gx_im
    1294         1084 :       b_re => qs_ot_env%buf1_k_k_sym
    1295         1084 :       b_im => qs_ot_env%buf2_k_k_sym
    1296         1084 :       tmp_kk => qs_ot_env%buf3_k_k_sym
    1297         1084 :       q_re => qs_ot_env%buf1_n_k
    1298         1084 :       q_im => qs_ot_env%buf1_n_k_dp
    1299              : 
    1300         1084 :       CALL dbcsr_get_info(sc_re, nfullrows_total=n, nfullcols_total=k)
    1301              : 
    1302              :       ! Q = X*(X^H*S*X)^(-1/2), reconstructed from the current REF coordinate.
    1303              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, f_re, f_im, q_re, q_im, &
    1304         1084 :                                   g_re)
    1305              : 
    1306         1084 :       IF (qs_ot_env%settings%do_rotation) THEN
    1307          832 :          CALL qs_ot_generate_rotation_complex(qs_ot_env)
    1308              : 
    1309              :          ! dF/dU = Q^H*G_C for C=Q*U.
    1310              :          CALL dbcsr_multiply('T', 'N', 1.0_dp, q_re, hc_re, &
    1311          832 :                              0.0_dp, qs_ot_env%rot_mat_dedu)
    1312          832 :          CALL dbcsr_multiply('T', 'N', 1.0_dp, q_im, hc_im, 0.0_dp, tmp_kk)
    1313              :          CALL dbcsr_add(qs_ot_env%rot_mat_dedu, tmp_kk, &
    1314          832 :                         alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1315              : 
    1316              :          CALL dbcsr_multiply('T', 'N', 1.0_dp, q_re, hc_im, &
    1317          832 :                              0.0_dp, qs_ot_env%rot_mat_dedu_im)
    1318          832 :          CALL dbcsr_multiply('T', 'N', 1.0_dp, q_im, hc_re, 0.0_dp, tmp_kk)
    1319              :          CALL dbcsr_add(qs_ot_env%rot_mat_dedu_im, tmp_kk, &
    1320          832 :                         alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1321          832 :          CALL qs_ot_rot_mat_derivative_complex(qs_ot_env)
    1322              : 
    1323              :          ! The REF coordinate sees G_Q=G_C*U^H.
    1324          832 :          CALL dbcsr_copy(hc_rot_re, hc_re, name="hc_rot_re")
    1325          832 :          CALL dbcsr_copy(hc_rot_im, hc_im, name="hc_rot_im")
    1326          832 :          CALL dbcsr_copy(tmp_nk, hc_re, name="tmp_nk")
    1327              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, hc_re, qs_ot_env%rot_mat_u, &
    1328          832 :                              0.0_dp, hc_rot_re)
    1329              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, hc_im, qs_ot_env%rot_mat_u_im, &
    1330          832 :                              0.0_dp, tmp_nk)
    1331          832 :          CALL dbcsr_add(hc_rot_re, tmp_nk, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1332              : 
    1333              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, hc_im, qs_ot_env%rot_mat_u, &
    1334          832 :                              0.0_dp, hc_rot_im)
    1335              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, hc_re, qs_ot_env%rot_mat_u_im, &
    1336          832 :                              0.0_dp, tmp_nk)
    1337          832 :          CALL dbcsr_add(hc_rot_im, tmp_nk, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1338          832 :          hc_work_re => hc_rot_re
    1339          832 :          hc_work_im => hc_rot_im
    1340              :       END IF
    1341              : 
    1342              :       ! B = Q^H*G_Q.  For uniform fixed occupations B is Hermitian.
    1343         1084 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, q_re, hc_work_re, 0.0_dp, b_re)
    1344         1084 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, q_im, hc_work_im, 0.0_dp, tmp_kk)
    1345         1084 :       CALL dbcsr_add(b_re, tmp_kk, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1346              : 
    1347         1084 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, q_re, hc_work_im, 0.0_dp, b_im)
    1348         1084 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, q_im, hc_work_re, 0.0_dp, tmp_kk)
    1349         1084 :       CALL dbcsr_add(b_im, tmp_kk, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1350              : 
    1351         1084 :       IF (qs_ot_env%settings%eps_irac_filter_matrix > 0.0_dp) THEN
    1352            0 :          CALL dbcsr_filter(b_re, qs_ot_env%settings%eps_irac_filter_matrix)
    1353            0 :          CALL dbcsr_filter(b_im, qs_ot_env%settings%eps_irac_filter_matrix)
    1354              :       END IF
    1355              : 
    1356              :       ! S*Q = (S*X)*F.  G is temporary storage for this pair.
    1357         1084 :       CALL qs_ot_complex_multiply('N', 'N', sc_re, sc_im, f_re, f_im, g_re, g_im, q_re)
    1358              : 
    1359              :       ! Form (S*Q)*B.  The Q workspaces are free after B has been formed.
    1360         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, g_re, b_re, 0.0_dp, q_re)
    1361         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, g_im, b_im, 0.0_dp, q_im)
    1362         1084 :       CALL dbcsr_add(q_re, q_im, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1363              : 
    1364         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, g_re, b_im, 0.0_dp, q_im)
    1365         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, g_im, b_re, 0.0_dp, g_re)
    1366         1084 :       CALL dbcsr_add(q_im, g_re, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1367              : 
    1368         1084 :       CALL dbcsr_add(q_re, hc_work_re, alpha_scalar=-1.0_dp, beta_scalar=1.0_dp)
    1369         1084 :       CALL dbcsr_add(q_im, hc_work_im, alpha_scalar=-1.0_dp, beta_scalar=1.0_dp)
    1370              : 
    1371              :       ! Pull the physical gradient back to the finite REF coordinate: G_X = (G_C-S*Q*B)*F.
    1372         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, q_re, f_re, 0.0_dp, g_re)
    1373         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, q_im, f_im, 0.0_dp, g_im)
    1374         1084 :       CALL dbcsr_add(g_re, g_im, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1375              : 
    1376         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, q_re, f_im, 0.0_dp, g_im)
    1377         1084 :       CALL dbcsr_multiply('N', 'N', 1.0_dp, q_im, f_re, 0.0_dp, q_re)
    1378         1084 :       CALL dbcsr_add(g_im, q_re, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1379              : 
    1380         1084 :       IF (qs_ot_env%settings%do_rotation) THEN
    1381              :          CALL qs_ot_add_ref_vertical_response_complex(b_re, b_im, f_re, f_im, &
    1382          832 :                                                       sc_re, sc_im, g_re, g_im, qs_ot_env)
    1383              :       END IF
    1384              : 
    1385         1084 :       IF (qs_ot_env%settings%eps_irac_filter_matrix > 0.0_dp) THEN
    1386            0 :          CALL dbcsr_filter(g_re, qs_ot_env%settings%eps_irac_filter_matrix)
    1387            0 :          CALL dbcsr_filter(g_im, qs_ot_env%settings%eps_irac_filter_matrix)
    1388              :       END IF
    1389              : 
    1390         1084 :       IF (qs_ot_env%settings%do_rotation) THEN
    1391          832 :          CALL dbcsr_release(hc_rot_re)
    1392          832 :          CALL dbcsr_release(hc_rot_im)
    1393          832 :          CALL dbcsr_release(tmp_nk)
    1394              :       END IF
    1395              : 
    1396         1084 :       CALL timestop(handle)
    1397              : 
    1398         1084 :    END SUBROUTINE qs_ot_get_derivative_ref_complex
    1399              : 
    1400              : ! **************************************************************************************************
    1401              : !> \brief Add the vertical response of the finite complex polar REF map.
    1402              : !> \param a_re real part of Q^H G_Q
    1403              : !> \param a_im imaginary part of Q^H G_Q
    1404              : !> \param f_re real part of (X^H S X)^(-1/2)
    1405              : !> \param f_im imaginary part of (X^H S X)^(-1/2)
    1406              : !> \param sx_re real part of S X
    1407              : !> \param sx_im imaginary part of S X
    1408              : !> \param gradient_re real REF gradient, updated in place
    1409              : !> \param gradient_im imaginary REF gradient, updated in place
    1410              : !> \param qs_ot_env complex OT environment
    1411              : ! **************************************************************************************************
    1412          832 :    SUBROUTINE qs_ot_add_ref_vertical_response_complex(a_re, a_im, f_re, f_im, &
    1413              :                                                       sx_re, sx_im, gradient_re, gradient_im, &
    1414              :                                                       qs_ot_env)
    1415              :       TYPE(dbcsr_type)                                   :: a_re, a_im, f_re, f_im, sx_re, sx_im, &
    1416              :                                                             gradient_re, gradient_im
    1417              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1418              : 
    1419              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_add_ref_vertical_response_complex'
    1420              : 
    1421              :       INTEGER                                            :: col, handle, i, j, k, row
    1422          832 :       INTEGER, DIMENSION(:), POINTER                     :: col_blk_offset, row_blk_offset
    1423              :       LOGICAL                                            :: found_im, found_re
    1424              :       REAL(KIND=dp)                                      :: denominator
    1425          832 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: f_evals
    1426          832 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block_im, block_re
    1427              :       TYPE(dbcsr_iterator_type)                          :: iter
    1428              :       TYPE(dbcsr_type) :: anti_im, anti_re, f_work_im, f_work_re, inner_im, inner_re, sq_im, &
    1429              :          sq_re, tmp_kk, tmp_nk, v_im, v_re, vertical_im, vertical_re, work_im, work_re, z_im, z_re
    1430              : 
    1431          832 :       CALL timeset(routineN, handle)
    1432              : 
    1433          832 :       CALL dbcsr_get_info(a_re, nfullrows_total=k)
    1434          832 :       IF (k == 0) THEN
    1435            0 :          CALL timestop(handle)
    1436            0 :          RETURN
    1437              :       END IF
    1438              : 
    1439              :       ! Only the anti-Hermitian part of A=Q^H G_Q couples to the unitary
    1440              :       ! component of the polar differential.
    1441          832 :       CALL qs_ot_square_transpose(a_re, tmp_kk, qs_ot_env%rot_mat_u)
    1442          832 :       CALL dbcsr_copy(anti_re, a_re, name='anti_re')
    1443          832 :       CALL dbcsr_add(anti_re, tmp_kk, alpha_scalar=0.5_dp, beta_scalar=-0.5_dp)
    1444          832 :       CALL qs_ot_square_transpose(a_im, tmp_kk, qs_ot_env%rot_mat_u)
    1445          832 :       CALL dbcsr_copy(anti_im, a_im, name='anti_im')
    1446          832 :       CALL dbcsr_add(anti_im, tmp_kk, alpha_scalar=0.5_dp, beta_scalar=0.5_dp)
    1447              : 
    1448              :       ! P=(X^H S X)^(1/2)=F^(-1).  In the eigenbasis of F, solve
    1449              :       ! P Z + Z P = antiherm(A) element by element.
    1450          832 :       CALL dbcsr_copy(f_work_re, f_re, name='f_work_re')
    1451          832 :       CALL dbcsr_copy(f_work_im, f_im, name='f_work_im')
    1452          832 :       CALL dbcsr_copy(v_re, f_re, name='v_re')
    1453          832 :       CALL dbcsr_copy(v_im, f_im, name='v_im')
    1454         2496 :       ALLOCATE (f_evals(k))
    1455              :       CALL cp_dbcsr_heevd(matrix_re=f_work_re, matrix_im=f_work_im, &
    1456              :                           eigenvectors_re=v_re, eigenvectors_im=v_im, eigenvalues=f_evals, &
    1457          832 :                           para_env=qs_ot_env%para_env, blacs_env=qs_ot_env%blacs_env)
    1458         5276 :       IF (MINVAL(f_evals) <= EPSILON(1.0_dp)) THEN
    1459            0 :          CPABORT('Complex REF inverse square root is not positive definite')
    1460              :       END IF
    1461              : 
    1462              :       ! inner = V^H antiherm(A) V.
    1463          832 :       CALL dbcsr_copy(work_re, anti_re, name='work_re')
    1464          832 :       CALL dbcsr_copy(work_im, anti_im, name='work_im')
    1465          832 :       CALL qs_ot_complex_multiply('N', 'N', anti_re, anti_im, v_re, v_im, work_re, work_im, tmp_kk)
    1466              : 
    1467          832 :       CALL dbcsr_copy(inner_re, anti_re, name='inner_re')
    1468          832 :       CALL dbcsr_copy(inner_im, anti_im, name='inner_im')
    1469          832 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, v_re, work_re, 0.0_dp, inner_re)
    1470          832 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, v_im, work_im, 1.0_dp, inner_re)
    1471          832 :       CALL dbcsr_multiply('T', 'N', 1.0_dp, v_re, work_im, 0.0_dp, inner_im)
    1472          832 :       CALL dbcsr_multiply('T', 'N', -1.0_dp, v_im, work_re, 1.0_dp, inner_im)
    1473              : 
    1474              :       CALL dbcsr_get_info(inner_re, row_blk_offset=row_blk_offset, &
    1475          832 :                           col_blk_offset=col_blk_offset)
    1476          832 :       CALL dbcsr_iterator_start(iter, inner_re)
    1477         1249 :       DO WHILE (dbcsr_iterator_blocks_left(iter))
    1478          417 :          CALL dbcsr_iterator_next_block(iter, row, col)
    1479          417 :          CALL dbcsr_get_block_p(inner_re, row, col, block_re, found_re)
    1480          417 :          CALL dbcsr_get_block_p(inner_im, row, col, block_im, found_im)
    1481          417 :          CPASSERT(found_re .AND. found_im)
    1482         3473 :          DO i = 1, SIZE(block_re, 1)
    1483        21793 :          DO j = 1, SIZE(block_re, 2)
    1484              :             denominator = 1.0_dp/f_evals(row_blk_offset(row) + i - 1) + &
    1485        19152 :                           1.0_dp/f_evals(col_blk_offset(col) + j - 1)
    1486        19152 :             block_re(i, j) = block_re(i, j)/denominator
    1487        21376 :             block_im(i, j) = block_im(i, j)/denominator
    1488              :          END DO
    1489              :          END DO
    1490              :       END DO
    1491          832 :       CALL dbcsr_iterator_stop(iter)
    1492              : 
    1493              :       ! Z = V inner V^H.
    1494          832 :       CALL qs_ot_complex_multiply('N', 'N', v_re, v_im, inner_re, inner_im, work_re, work_im, tmp_kk)
    1495              : 
    1496          832 :       CALL dbcsr_copy(z_re, anti_re, name='z_re')
    1497          832 :       CALL dbcsr_copy(z_im, anti_im, name='z_im')
    1498          832 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, work_re, v_re, 0.0_dp, z_re)
    1499          832 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, work_im, v_im, 1.0_dp, z_re)
    1500          832 :       CALL dbcsr_multiply('N', 'T', 1.0_dp, work_im, v_re, 0.0_dp, z_im)
    1501          832 :       CALL dbcsr_multiply('N', 'T', -1.0_dp, work_re, v_im, 1.0_dp, z_im)
    1502              : 
    1503              :       ! The adjoint vertical contribution is 2 S Q Z, with S Q=(S X)F.
    1504          832 :       CALL dbcsr_copy(sq_re, sx_re, name='sq_re')
    1505          832 :       CALL dbcsr_copy(sq_im, sx_im, name='sq_im')
    1506          832 :       CALL dbcsr_copy(tmp_nk, sx_re, name='tmp_nk')
    1507          832 :       CALL qs_ot_complex_multiply('N', 'N', sx_re, sx_im, f_re, f_im, sq_re, sq_im, tmp_nk)
    1508              : 
    1509          832 :       CALL dbcsr_copy(vertical_re, sx_re, name='vertical_re')
    1510          832 :       CALL dbcsr_copy(vertical_im, sx_im, name='vertical_im')
    1511          832 :       CALL qs_ot_complex_multiply('N', 'N', sq_re, sq_im, z_re, z_im, vertical_re, vertical_im, tmp_nk)
    1512          832 :       CALL dbcsr_add(gradient_re, vertical_re, alpha_scalar=1.0_dp, beta_scalar=2.0_dp)
    1513          832 :       CALL dbcsr_add(gradient_im, vertical_im, alpha_scalar=1.0_dp, beta_scalar=2.0_dp)
    1514              : 
    1515          832 :       DEALLOCATE (f_evals)
    1516          832 :       CALL dbcsr_release(anti_im)
    1517          832 :       CALL dbcsr_release(anti_re)
    1518          832 :       CALL dbcsr_release(f_work_im)
    1519          832 :       CALL dbcsr_release(f_work_re)
    1520          832 :       CALL dbcsr_release(inner_im)
    1521          832 :       CALL dbcsr_release(inner_re)
    1522          832 :       CALL dbcsr_release(sq_im)
    1523          832 :       CALL dbcsr_release(sq_re)
    1524          832 :       CALL dbcsr_release(tmp_kk)
    1525          832 :       CALL dbcsr_release(tmp_nk)
    1526          832 :       CALL dbcsr_release(v_im)
    1527          832 :       CALL dbcsr_release(v_re)
    1528          832 :       CALL dbcsr_release(vertical_im)
    1529          832 :       CALL dbcsr_release(vertical_re)
    1530          832 :       CALL dbcsr_release(work_im)
    1531          832 :       CALL dbcsr_release(work_re)
    1532          832 :       CALL dbcsr_release(z_im)
    1533          832 :       CALL dbcsr_release(z_re)
    1534              : 
    1535          832 :       CALL timestop(handle)
    1536              : 
    1537         1664 :    END SUBROUTINE qs_ot_add_ref_vertical_response_complex
    1538              : 
    1539              : ! **************************************************************************************************
    1540              : !> \brief computes U=exp(A) for the complex anti-Hermitian generator
    1541              : !>        A=rot_mat_x+i*rot_mat_x_im
    1542              : !> \param qs_ot_env a complex k-point OT environment
    1543              : ! **************************************************************************************************
    1544         3350 :    SUBROUTINE qs_ot_generate_rotation_complex(qs_ot_env)
    1545              : 
    1546              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1547              : 
    1548              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_generate_rotation_complex'
    1549              : 
    1550              :       INTEGER                                            :: handle, k
    1551              :       REAL(KIND=dp)                                      :: rot_norm
    1552         3350 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: exp_evals_im, exp_evals_re
    1553              :       TYPE(dbcsr_type)                                   :: h_re, tmp, w_im, w_re
    1554              : 
    1555         3350 :       CALL timeset(routineN, handle)
    1556              : 
    1557         3350 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    1558         3350 :       CPASSERT(ASSOCIATED(qs_ot_env%rot_mat_x_im))
    1559         3350 :       CPASSERT(ASSOCIATED(qs_ot_env%rot_mat_u_im))
    1560              : 
    1561         3350 :       CALL dbcsr_get_info(qs_ot_env%rot_mat_x, nfullrows_total=k)
    1562         3350 :       IF (k /= 0) THEN
    1563              :          rot_norm = dbcsr_frobenius_norm(qs_ot_env%rot_mat_x) + &
    1564         3350 :                     dbcsr_frobenius_norm(qs_ot_env%rot_mat_x_im)
    1565         3350 :          IF (rot_norm <= EPSILON(1.0_dp)) THEN
    1566          744 :             CALL dbcsr_set(qs_ot_env%rot_mat_u, 0.0_dp)
    1567          744 :             CALL dbcsr_add_on_diag(qs_ot_env%rot_mat_u, 1.0_dp)
    1568          744 :             CALL dbcsr_set(qs_ot_env%rot_mat_u_im, 0.0_dp)
    1569          744 :             CALL timestop(handle)
    1570              :             RETURN
    1571              :          END IF
    1572              : 
    1573              :          ! i*A = i*X-Y is Hermitian. Its eigenvectors give
    1574              :          ! exp(A)=V*diag(exp(-i*lambda))*V^H.
    1575         2606 :          CALL dbcsr_copy(h_re, qs_ot_env%rot_mat_x_im, name="h_re")
    1576         2606 :          CALL dbcsr_scale(h_re, -1.0_dp)
    1577              :          CALL cp_dbcsr_heevd(matrix_re=h_re, matrix_im=qs_ot_env%rot_mat_x, &
    1578              :                              eigenvectors_re=qs_ot_env%rot_mat_evec_re, &
    1579              :                              eigenvectors_im=qs_ot_env%rot_mat_evec_im, &
    1580              :                              eigenvalues=qs_ot_env%rot_mat_evals, &
    1581         2606 :                              para_env=qs_ot_env%para_env, blacs_env=qs_ot_env%blacs_env)
    1582              : 
    1583        10424 :          ALLOCATE (exp_evals_re(k), exp_evals_im(k))
    1584        20554 :          exp_evals_re(:) = COS(-qs_ot_env%rot_mat_evals(:))
    1585        20554 :          exp_evals_im(:) = SIN(-qs_ot_env%rot_mat_evals(:))
    1586              : 
    1587         2606 :          CALL dbcsr_copy(w_re, qs_ot_env%rot_mat_evec_re, name="w_re")
    1588         2606 :          CALL dbcsr_scale_by_vector(w_re, alpha=exp_evals_re, side='right')
    1589         2606 :          CALL dbcsr_copy(tmp, qs_ot_env%rot_mat_evec_im, name="tmp")
    1590         2606 :          CALL dbcsr_scale_by_vector(tmp, alpha=exp_evals_im, side='right')
    1591         2606 :          CALL dbcsr_add(w_re, tmp, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1592              : 
    1593         2606 :          CALL dbcsr_copy(w_im, qs_ot_env%rot_mat_evec_im, name="w_im")
    1594         2606 :          CALL dbcsr_scale_by_vector(w_im, alpha=exp_evals_re, side='right')
    1595         2606 :          CALL dbcsr_copy(tmp, qs_ot_env%rot_mat_evec_re)
    1596         2606 :          CALL dbcsr_scale_by_vector(tmp, alpha=exp_evals_im, side='right')
    1597         2606 :          CALL dbcsr_add(w_im, tmp, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1598              : 
    1599              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, w_re, qs_ot_env%rot_mat_evec_re, &
    1600         2606 :                              0.0_dp, qs_ot_env%rot_mat_u)
    1601              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, w_im, qs_ot_env%rot_mat_evec_im, &
    1602         2606 :                              1.0_dp, qs_ot_env%rot_mat_u)
    1603              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, w_im, qs_ot_env%rot_mat_evec_re, &
    1604         2606 :                              0.0_dp, qs_ot_env%rot_mat_u_im)
    1605              :          CALL dbcsr_multiply('N', 'T', -1.0_dp, w_re, qs_ot_env%rot_mat_evec_im, &
    1606         2606 :                              1.0_dp, qs_ot_env%rot_mat_u_im)
    1607              : 
    1608         2606 :          CALL dbcsr_release(h_re)
    1609         2606 :          CALL dbcsr_release(tmp)
    1610         2606 :          CALL dbcsr_release(w_re)
    1611         2606 :          CALL dbcsr_release(w_im)
    1612         2606 :          DEALLOCATE (exp_evals_re, exp_evals_im)
    1613              :       END IF
    1614              : 
    1615         2606 :       CALL timestop(handle)
    1616              : 
    1617         5956 :    END SUBROUTINE qs_ot_generate_rotation_complex
    1618              : 
    1619              : ! **************************************************************************************************
    1620              : !> \brief pull the complex dE/dU covector back to the anti-Hermitian generator
    1621              : !>        using the adjoint Frechet derivative of exp
    1622              : !> \param qs_ot_env a complex k-point OT environment with an up-to-date U
    1623              : ! **************************************************************************************************
    1624         2932 :    SUBROUTINE qs_ot_rot_mat_derivative_complex(qs_ot_env)
    1625              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1626              : 
    1627              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_rot_mat_derivative_complex'
    1628              : 
    1629              :       INTEGER                                            :: handle, k
    1630              :       REAL(KIND=dp)                                      :: rot_norm
    1631              :       TYPE(dbcsr_type)                                   :: frechet_im, frechet_re, inner_deriv_im, &
    1632              :                                                             inner_deriv_re, outer_deriv_im, &
    1633              :                                                             outer_deriv_re, tmp, work_im, work_re
    1634              : 
    1635         1466 :       CALL timeset(routineN, handle)
    1636              : 
    1637         1466 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    1638         1466 :       CPASSERT(ASSOCIATED(qs_ot_env%rot_mat_dedu_im))
    1639         1466 :       CPASSERT(ASSOCIATED(qs_ot_env%rot_mat_gx_im))
    1640              : 
    1641         1466 :       CALL dbcsr_get_info(qs_ot_env%rot_mat_u, nfullrows_total=k)
    1642         1466 :       IF (k /= 0) THEN
    1643              :          rot_norm = dbcsr_frobenius_norm(qs_ot_env%rot_mat_x) + &
    1644         1466 :                     dbcsr_frobenius_norm(qs_ot_env%rot_mat_x_im)
    1645         1466 :          IF (rot_norm <= EPSILON(1.0_dp)) THEN
    1646          370 :             CALL qs_ot_square_transpose(qs_ot_env%rot_mat_dedu, tmp, qs_ot_env%rot_mat_u)
    1647          370 :             CALL dbcsr_copy(qs_ot_env%rot_mat_gx, qs_ot_env%rot_mat_dedu)
    1648              :             CALL dbcsr_add(qs_ot_env%rot_mat_gx, tmp, &
    1649          370 :                            alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1650          370 :             CALL dbcsr_release(tmp)
    1651              : 
    1652          370 :             CALL qs_ot_square_transpose(qs_ot_env%rot_mat_dedu_im, tmp, qs_ot_env%rot_mat_u)
    1653          370 :             CALL dbcsr_copy(qs_ot_env%rot_mat_gx_im, qs_ot_env%rot_mat_dedu_im)
    1654              :             CALL dbcsr_add(qs_ot_env%rot_mat_gx_im, tmp, &
    1655          370 :                            alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1656          370 :             CALL dbcsr_release(tmp)
    1657          370 :             CALL timestop(handle)
    1658          370 :             RETURN
    1659              :          END IF
    1660              : 
    1661         1096 :          CALL dbcsr_copy(work_re, qs_ot_env%rot_mat_dedu, name="work_re")
    1662         1096 :          CALL dbcsr_copy(work_im, qs_ot_env%rot_mat_dedu, name="work_im")
    1663         1096 :          CALL dbcsr_copy(tmp, qs_ot_env%rot_mat_dedu, name="tmp")
    1664         1096 :          CALL dbcsr_copy(inner_deriv_re, qs_ot_env%rot_mat_dedu, name="inner_deriv_re")
    1665         1096 :          CALL dbcsr_copy(inner_deriv_im, qs_ot_env%rot_mat_dedu, name="inner_deriv_im")
    1666              : 
    1667              :          ! V^H*(dE/dU)*V, split into real and imaginary parts.
    1668              :          CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%rot_mat_dedu, qs_ot_env%rot_mat_dedu_im, &
    1669              :                                      qs_ot_env%rot_mat_evec_re, qs_ot_env%rot_mat_evec_im, work_re, work_im, &
    1670         1096 :                                      tmp)
    1671              :          CALL dbcsr_multiply('T', 'N', 1.0_dp, qs_ot_env%rot_mat_evec_re, &
    1672         1096 :                              work_re, 0.0_dp, inner_deriv_re)
    1673              :          CALL dbcsr_multiply('T', 'N', 1.0_dp, qs_ot_env%rot_mat_evec_im, &
    1674         1096 :                              work_im, 1.0_dp, inner_deriv_re)
    1675              :          CALL dbcsr_multiply('T', 'N', 1.0_dp, qs_ot_env%rot_mat_evec_re, &
    1676         1096 :                              work_im, 0.0_dp, inner_deriv_im)
    1677              :          CALL dbcsr_multiply('T', 'N', -1.0_dp, qs_ot_env%rot_mat_evec_im, &
    1678         1096 :                              work_re, 1.0_dp, inner_deriv_im)
    1679              : 
    1680              :          CALL qs_ot_apply_complex_frechet_dbcsr(qs_ot_env%rot_mat_evals, &
    1681              :                                                 inner_deriv_re, inner_deriv_im, &
    1682         1096 :                                                 outer_deriv_re, outer_deriv_im, adjoint=.TRUE.)
    1683              : 
    1684              :          CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%rot_mat_evec_re, qs_ot_env%rot_mat_evec_im, &
    1685         1096 :                                      outer_deriv_re, outer_deriv_im, work_re, work_im, tmp)
    1686              : 
    1687         1096 :          CALL dbcsr_copy(frechet_re, qs_ot_env%rot_mat_dedu, name="frechet_re")
    1688         1096 :          CALL dbcsr_copy(frechet_im, qs_ot_env%rot_mat_dedu, name="frechet_im")
    1689              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, work_re, qs_ot_env%rot_mat_evec_re, &
    1690         1096 :                              0.0_dp, frechet_re)
    1691              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, work_im, qs_ot_env%rot_mat_evec_im, &
    1692         1096 :                              1.0_dp, frechet_re)
    1693              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, work_im, qs_ot_env%rot_mat_evec_re, &
    1694         1096 :                              0.0_dp, frechet_im)
    1695              :          CALL dbcsr_multiply('N', 'T', -1.0_dp, work_re, qs_ot_env%rot_mat_evec_im, &
    1696         1096 :                              1.0_dp, frechet_im)
    1697              : 
    1698              :          ! Tangents satisfy X^T=-X and Y^T=Y for A=X+iY.
    1699         1096 :          CALL qs_ot_square_transpose(frechet_re, tmp, qs_ot_env%rot_mat_u)
    1700         1096 :          CALL dbcsr_copy(qs_ot_env%rot_mat_gx, frechet_re)
    1701              :          CALL dbcsr_add(qs_ot_env%rot_mat_gx, tmp, &
    1702         1096 :                         alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1703         1096 :          CALL qs_ot_square_transpose(frechet_im, tmp, qs_ot_env%rot_mat_u)
    1704         1096 :          CALL dbcsr_copy(qs_ot_env%rot_mat_gx_im, frechet_im)
    1705              :          CALL dbcsr_add(qs_ot_env%rot_mat_gx_im, tmp, &
    1706         1096 :                         alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1707              : 
    1708         1096 :          CALL dbcsr_release(frechet_re)
    1709         1096 :          CALL dbcsr_release(frechet_im)
    1710         1096 :          CALL dbcsr_release(inner_deriv_re)
    1711         1096 :          CALL dbcsr_release(inner_deriv_im)
    1712         1096 :          CALL dbcsr_release(outer_deriv_re)
    1713         1096 :          CALL dbcsr_release(outer_deriv_im)
    1714         1096 :          CALL dbcsr_release(tmp)
    1715         1096 :          CALL dbcsr_release(work_re)
    1716         1096 :          CALL dbcsr_release(work_im)
    1717              :       END IF
    1718              : 
    1719         1096 :       CALL timestop(handle)
    1720              : 
    1721         1466 :    END SUBROUTINE qs_ot_rot_mat_derivative_complex
    1722              : 
    1723              : ! **************************************************************************************************
    1724              : !> \brief compute P=X^H*S*X and the STRICT matrix functions for a complex K-point channel
    1725              : !> \param matrix_x real part of X
    1726              : !> \param matrix_x_im imaginary part of X
    1727              : !> \param matrix_sx real part of S*X
    1728              : !> \param matrix_sx_im imaginary part of S*X
    1729              : !> \param qs_ot_env OT channel state
    1730              : !> \param zero_coordinates initialize the known X=0 state without diagonalization
    1731              : ! **************************************************************************************************
    1732         1982 :    SUBROUTINE qs_ot_get_p_complex(matrix_x, matrix_x_im, matrix_sx, matrix_sx_im, qs_ot_env, zero_coordinates)
    1733              :       TYPE(dbcsr_type), POINTER                          :: matrix_x, matrix_x_im, matrix_sx, &
    1734              :                                                             matrix_sx_im
    1735              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1736              :       LOGICAL, INTENT(IN), OPTIONAL                      :: zero_coordinates
    1737              : 
    1738              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_get_p_complex'
    1739              : 
    1740              :       INTEGER                                            :: handle
    1741              : 
    1742         1982 :       CALL timeset(routineN, handle)
    1743              : 
    1744         1982 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    1745         1982 :       CPASSERT(qs_ot_env%settings%ot_algorithm == 'TOD')
    1746              : 
    1747         1982 :       IF (PRESENT(zero_coordinates)) THEN
    1748          408 :          IF (zero_coordinates) THEN
    1749          408 :             CALL dbcsr_set(qs_ot_env%matrix_p, 0.0_dp)
    1750          408 :             CALL dbcsr_set(qs_ot_env%matrix_p_im, 0.0_dp)
    1751          408 :             CALL dbcsr_set(qs_ot_env%matrix_r, 0.0_dp)
    1752          408 :             CALL dbcsr_add_on_diag(qs_ot_env%matrix_r, 1.0_dp)
    1753          408 :             CALL dbcsr_set(qs_ot_env%matrix_r_im, 0.0_dp)
    1754          408 :             CALL dbcsr_copy(qs_ot_env%matrix_cosp, qs_ot_env%matrix_r)
    1755          408 :             CALL dbcsr_copy(qs_ot_env%matrix_sinp, qs_ot_env%matrix_r)
    1756          408 :             CALL dbcsr_set(qs_ot_env%matrix_cosp_im, 0.0_dp)
    1757          408 :             CALL dbcsr_set(qs_ot_env%matrix_sinp_im, 0.0_dp)
    1758          408 :             CALL dbcsr_reserve_all_blocks(qs_ot_env%matrix_cosp_b)
    1759          408 :             CALL dbcsr_reserve_all_blocks(qs_ot_env%matrix_sinp_b)
    1760          408 :             CALL dbcsr_set(qs_ot_env%matrix_cosp_b, -0.5_dp)
    1761          408 :             CALL dbcsr_set(qs_ot_env%matrix_sinp_b, -1.0_dp/6.0_dp)
    1762         2192 :             qs_ot_env%evals(:) = 0.0_dp
    1763         2192 :             qs_ot_env%dum(:) = 0.0_dp
    1764          408 :             qs_ot_env%do_taylor = .FALSE.
    1765          408 :             CALL timestop(handle)
    1766          408 :             RETURN
    1767              :          END IF
    1768              :       END IF
    1769              : 
    1770              :       CALL qs_ot_complex_multiply('C', 'N', matrix_x, matrix_x_im, matrix_sx, matrix_sx_im, &
    1771         1574 :                                   qs_ot_env%matrix_p, qs_ot_env%matrix_p_im, qs_ot_env%matrix_buf1)
    1772         1574 :       qs_ot_env%do_taylor = .FALSE.
    1773         1574 :       CALL qs_ot_p2m_diag(qs_ot_env)
    1774              : 
    1775         1574 :       CALL timestop(handle)
    1776              : 
    1777              :    END SUBROUTINE qs_ot_get_p_complex
    1778              : 
    1779              : ! **************************************************************************************************
    1780              : !> \brief update complex K-point orbitals with the finite STRICT transformation
    1781              : !> \param matrix_c real output orbitals
    1782              : !> \param matrix_c_im imaginary output orbitals
    1783              : !> \param matrix_s real overlap matrix
    1784              : !> \param matrix_s_im imaginary overlap matrix
    1785              : !> \param qs_ot_env OT channel state
    1786              : ! **************************************************************************************************
    1787         1572 :    SUBROUTINE qs_ot_get_orbitals_complex(matrix_c, matrix_c_im, matrix_s, matrix_s_im, qs_ot_env)
    1788              :       TYPE(dbcsr_type), POINTER                          :: matrix_c, matrix_c_im, matrix_s, &
    1789              :                                                             matrix_s_im
    1790              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1791              : 
    1792              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_get_orbitals_complex'
    1793              : 
    1794              :       INTEGER                                            :: handle
    1795              :       TYPE(dbcsr_type)                                   :: rotated_im, rotated_re, rotation_tmp
    1796              : 
    1797         1572 :       CALL timeset(routineN, handle)
    1798              : 
    1799         1572 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    1800         1572 :       CPASSERT(qs_ot_env%settings%ot_algorithm == 'TOD')
    1801              : 
    1802              :       CALL qs_ot_complex_multiply('N', 'N', matrix_s, matrix_s_im, &
    1803              :                                   qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, &
    1804              :                                   qs_ot_env%matrix_sx, qs_ot_env%matrix_sx_im, &
    1805         1572 :                                   qs_ot_env%matrix_tmp_nk)
    1806              :       CALL qs_ot_get_p_complex(qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, &
    1807         1572 :                                qs_ot_env%matrix_sx, qs_ot_env%matrix_sx_im, qs_ot_env)
    1808              : 
    1809              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_c0, qs_ot_env%matrix_c0_im, &
    1810              :                                   qs_ot_env%matrix_cosp, qs_ot_env%matrix_cosp_im, &
    1811         1572 :                                   matrix_c, matrix_c_im, qs_ot_env%matrix_tmp_nk)
    1812              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, &
    1813              :                                   qs_ot_env%matrix_sinp, qs_ot_env%matrix_sinp_im, &
    1814              :                                   qs_ot_env%matrix_buf_nk, qs_ot_env%matrix_buf_nk_im, &
    1815         1572 :                                   qs_ot_env%matrix_tmp_nk)
    1816         1572 :       CALL dbcsr_add(matrix_c, qs_ot_env%matrix_buf_nk, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1817         1572 :       CALL dbcsr_add(matrix_c_im, qs_ot_env%matrix_buf_nk_im, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    1818              : 
    1819         1572 :       IF (qs_ot_env%settings%do_rotation) THEN
    1820          612 :          CALL qs_ot_generate_rotation_complex(qs_ot_env)
    1821              : 
    1822          612 :          CALL dbcsr_copy(rotated_re, matrix_c, name="strict_rotated_re")
    1823          612 :          CALL dbcsr_copy(rotated_im, matrix_c_im, name="strict_rotated_im")
    1824          612 :          CALL dbcsr_copy(rotation_tmp, matrix_c, name="strict_rotation_tmp")
    1825              : 
    1826              :          ! C_out = Q(X)*U for the finite STRICT chart Q(X).
    1827              :          CALL qs_ot_complex_multiply('N', 'N', matrix_c, matrix_c_im, qs_ot_env%rot_mat_u, &
    1828          612 :                                      qs_ot_env%rot_mat_u_im, rotated_re, rotated_im, rotation_tmp)
    1829              : 
    1830          612 :          CALL dbcsr_copy(matrix_c, rotated_re)
    1831          612 :          CALL dbcsr_copy(matrix_c_im, rotated_im)
    1832          612 :          CALL dbcsr_release(rotated_re)
    1833          612 :          CALL dbcsr_release(rotated_im)
    1834          612 :          CALL dbcsr_release(rotation_tmp)
    1835              :       END IF
    1836              : 
    1837         1572 :       CALL timestop(handle)
    1838              : 
    1839         1572 :    END SUBROUTINE qs_ot_get_orbitals_complex
    1840              : 
    1841              : ! **************************************************************************************************
    1842              : !> \brief Project a complex STRICT gradient; real minimizer fallbacks use the shared real projection.
    1843              : !> \param qs_ot_env OT channel state
    1844              : !> \param matrix_gx real component to project in place
    1845              : !> \param matrix_gx_im imaginary component for a complex channel
    1846              : !> \param use_preconditioner use the attached preconditioner, default true
    1847              : ! **************************************************************************************************
    1848         1306 :    SUBROUTINE qs_ot_project_gradient(qs_ot_env, matrix_gx, matrix_gx_im, use_preconditioner)
    1849              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1850              :       TYPE(dbcsr_type), POINTER                          :: matrix_gx
    1851              :       TYPE(dbcsr_type), OPTIONAL, POINTER                :: matrix_gx_im
    1852              :       LOGICAL, INTENT(IN), OPTIONAL                      :: use_preconditioner
    1853              : 
    1854              :       LOGICAL                                            :: preconditioned
    1855              :       TYPE(dbcsr_type), POINTER                          :: target_im, target_re
    1856              : 
    1857         1306 :       IF (.NOT. qs_ot_env%has_complex_kpoint_state) THEN
    1858            0 :          CALL qs_ot_project_gradient_real(qs_ot_env, matrix_gx, use_preconditioner)
    1859            0 :          RETURN
    1860              :       END IF
    1861         1306 :       preconditioned = .TRUE.
    1862         1306 :       IF (PRESENT(use_preconditioner)) preconditioned = use_preconditioner
    1863         1306 :       IF (.NOT. preconditioned) qs_ot_env%os_valid = .FALSE.
    1864         1306 :       CPASSERT(PRESENT(matrix_gx_im))
    1865         1306 :       CALL qs_ot_prepare_complex_tangent_metric(qs_ot_env, use_preconditioner=preconditioned)
    1866         1306 :       IF (preconditioned .AND. ASSOCIATED(qs_ot_env%preconditioner)) THEN
    1867         1272 :          target_re => qs_ot_env%matrix_psc0
    1868         1272 :          target_im => qs_ot_env%matrix_psc0_im
    1869              :       ELSE
    1870           34 :          target_re => qs_ot_env%matrix_sc0
    1871           34 :          target_im => qs_ot_env%matrix_sc0_im
    1872              :       END IF
    1873              : 
    1874              :       CALL qs_ot_complex_multiply('C', 'N', target_re, target_im, &
    1875              :                                   matrix_gx, matrix_gx_im, &
    1876              :                                   qs_ot_env%matrix_buf1_ortho, qs_ot_env%matrix_buf1_ortho_im, &
    1877         1306 :                                   qs_ot_env%matrix_tmp_ortho)
    1878              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_os, qs_ot_env%matrix_os_im, &
    1879              :                                   qs_ot_env%matrix_buf1_ortho, qs_ot_env%matrix_buf1_ortho_im, &
    1880              :                                   qs_ot_env%matrix_buf2_ortho, qs_ot_env%matrix_buf2_ortho_im, &
    1881         1306 :                                   qs_ot_env%matrix_tmp_ortho)
    1882              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_sc0, qs_ot_env%matrix_sc0_im, &
    1883              :                                   qs_ot_env%matrix_buf2_ortho, qs_ot_env%matrix_buf2_ortho_im, &
    1884              :                                   qs_ot_env%matrix_buf_nk, qs_ot_env%matrix_buf_nk_im, &
    1885         1306 :                                   qs_ot_env%matrix_tmp_nk)
    1886              :       CALL dbcsr_add(matrix_gx, qs_ot_env%matrix_buf_nk, &
    1887         1306 :                      alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1888              :       CALL dbcsr_add(matrix_gx_im, qs_ot_env%matrix_buf_nk_im, &
    1889         1306 :                      alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    1890              : 
    1891              :    END SUBROUTINE qs_ot_project_gradient
    1892              : 
    1893              : ! **************************************************************************************************
    1894              : !> \brief Prepare the inverse metric used to project a complex STRICT gradient.
    1895              : !>        An unusable preconditioner is detached before any minimizer history is updated.
    1896              : !> \param qs_ot_env OT channel state
    1897              : !> \param preconditioner_rejected true if the attached preconditioner was not positive definite
    1898              : !> \param use_preconditioner use the attached preconditioner, default true
    1899              : ! **************************************************************************************************
    1900         6630 :    SUBROUTINE qs_ot_prepare_complex_tangent_metric(qs_ot_env, preconditioner_rejected, use_preconditioner)
    1901              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    1902              :       LOGICAL, INTENT(OUT), OPTIONAL                     :: preconditioner_rejected
    1903              :       LOGICAL, INTENT(IN), OPTIONAL                      :: use_preconditioner
    1904              : 
    1905              :       INTEGER                                            :: i, k
    1906              :       LOGICAL                                            :: preconditioned
    1907              :       REAL(KIND=dp)                                      :: eval_scale, eval_threshold
    1908              :       TYPE(dbcsr_type), POINTER                          :: target_im, target_re
    1909              : 
    1910         6454 :       preconditioned = .TRUE.
    1911         6454 :       IF (PRESENT(use_preconditioner)) preconditioned = use_preconditioner
    1912         1306 :       IF (.NOT. preconditioned) qs_ot_env%os_valid = .FALSE.
    1913         6454 :       IF (PRESENT(preconditioner_rejected)) preconditioner_rejected = .FALSE.
    1914         6454 :       IF (qs_ot_env%os_valid) RETURN
    1915              : 
    1916          176 :       IF (preconditioned .AND. ASSOCIATED(qs_ot_env%preconditioner)) THEN
    1917          166 :          target_re => qs_ot_env%matrix_psc0
    1918          166 :          target_im => qs_ot_env%matrix_psc0_im
    1919              :          CALL apply_preconditioner(qs_ot_env%preconditioner, &
    1920              :                                    qs_ot_env%matrix_sc0, qs_ot_env%matrix_sc0_im, &
    1921          166 :                                    target_re, target_im)
    1922              :       ELSE
    1923           10 :          target_re => qs_ot_env%matrix_sc0
    1924           10 :          target_im => qs_ot_env%matrix_sc0_im
    1925              :       END IF
    1926              :       CALL qs_ot_complex_multiply('C', 'N', qs_ot_env%matrix_sc0, qs_ot_env%matrix_sc0_im, &
    1927              :                                   target_re, target_im, qs_ot_env%matrix_os, qs_ot_env%matrix_os_im, &
    1928          176 :                                   qs_ot_env%matrix_buf1)
    1929          176 :       CALL dbcsr_get_info(qs_ot_env%matrix_os, nfullrows_total=k)
    1930              :       ! Preserve the eigenvectors and eigenvalues of X^H*S*X used by the STRICT derivative.
    1931              :       CALL cp_dbcsr_heevd(matrix_re=qs_ot_env%matrix_os, matrix_im=qs_ot_env%matrix_os_im, &
    1932              :                           eigenvectors_re=qs_ot_env%matrix_buf3, eigenvectors_im=qs_ot_env%matrix_buf3_im, &
    1933              :                           eigenvalues=qs_ot_env%dum, para_env=qs_ot_env%para_env, &
    1934          176 :                           blacs_env=qs_ot_env%blacs_env)
    1935         1320 :       eval_scale = MAX(1.0_dp, MAXVAL(ABS(qs_ot_env%dum(1:k))))
    1936          176 :       eval_threshold = 100.0_dp*EPSILON(1.0_dp)*eval_scale
    1937              :       IF (MINVAL(qs_ot_env%dum(1:k)) <= eval_threshold .AND. &
    1938         1320 :           preconditioned .AND. ASSOCIATED(qs_ot_env%preconditioner)) THEN
    1939            0 :          NULLIFY (qs_ot_env%preconditioner)
    1940            0 :          qs_ot_env%preconditioner_changed = .TRUE.
    1941            0 :          IF (PRESENT(preconditioner_rejected)) preconditioner_rejected = .TRUE.
    1942            0 :          target_re => qs_ot_env%matrix_sc0
    1943            0 :          target_im => qs_ot_env%matrix_sc0_im
    1944              :          CALL qs_ot_complex_multiply('C', 'N', qs_ot_env%matrix_sc0, qs_ot_env%matrix_sc0_im, &
    1945              :                                      target_re, target_im, qs_ot_env%matrix_os, qs_ot_env%matrix_os_im, &
    1946            0 :                                      qs_ot_env%matrix_buf1)
    1947              :          CALL cp_dbcsr_heevd(matrix_re=qs_ot_env%matrix_os, matrix_im=qs_ot_env%matrix_os_im, &
    1948              :                              eigenvectors_re=qs_ot_env%matrix_buf3, eigenvectors_im=qs_ot_env%matrix_buf3_im, &
    1949              :                              eigenvalues=qs_ot_env%dum, para_env=qs_ot_env%para_env, &
    1950            0 :                              blacs_env=qs_ot_env%blacs_env)
    1951            0 :          eval_scale = MAX(1.0_dp, MAXVAL(ABS(qs_ot_env%dum(1:k))))
    1952            0 :          eval_threshold = 100.0_dp*EPSILON(1.0_dp)*eval_scale
    1953              :       END IF
    1954         1320 :       CPASSERT(MINVAL(qs_ot_env%dum(1:k)) > eval_threshold)
    1955         1320 :       DO i = 1, k
    1956         1320 :          qs_ot_env%dum(i) = 1.0_dp/qs_ot_env%dum(i)
    1957              :       END DO
    1958          176 :       CALL dbcsr_copy(qs_ot_env%matrix_buf1, qs_ot_env%matrix_buf3)
    1959          176 :       CALL dbcsr_copy(qs_ot_env%matrix_buf1_im, qs_ot_env%matrix_buf3_im)
    1960          176 :       CALL dbcsr_scale_by_vector(qs_ot_env%matrix_buf1, alpha=qs_ot_env%dum, side='right')
    1961          176 :       CALL dbcsr_scale_by_vector(qs_ot_env%matrix_buf1_im, alpha=qs_ot_env%dum, side='right')
    1962              :       CALL qs_ot_complex_multiply('N', 'C', qs_ot_env%matrix_buf1, qs_ot_env%matrix_buf1_im, &
    1963              :                                   qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf3_im, &
    1964              :                                   qs_ot_env%matrix_os, qs_ot_env%matrix_os_im, &
    1965          176 :                                   qs_ot_env%matrix_buf2)
    1966              :       ! An identity-metric projection must not replace the cached preconditioned metric.
    1967          176 :       qs_ot_env%os_valid = preconditioned
    1968              : 
    1969              :    END SUBROUTINE qs_ot_prepare_complex_tangent_metric
    1970              : 
    1971              : ! **************************************************************************************************
    1972              : !> \brief Prepare uncached single-rank STRICT metrics in bounded OpenMP batches.
    1973              : !>        Leave unsuccessful channels to the scalar path so preconditioner rejection and history
    1974              : !>        invalidation retain their existing order. Workers use only private dense storage.
    1975              : !> \param qs_ot_env local OT channels
    1976              : ! **************************************************************************************************
    1977        96868 :    SUBROUTINE qs_ot_prepare_complex_tangent_metric_batch(qs_ot_env)
    1978              :       TYPE(qs_ot_type), INTENT(INOUT)                    :: qs_ot_env(:)
    1979              : 
    1980              :       INTEGER                                            :: first, i, j, k, n, nbatch, nchannels, &
    1981              :                                                             nworkers
    1982        96868 :       INTEGER, ALLOCATABLE                               :: channels(:)
    1983              :       LOGICAL                                            :: handled
    1984              :       REAL(dp)                                           :: eval_threshold
    1985        96868 :       TYPE(dbcsr_p_type), ALLOCATABLE                    :: matrix_in(:, :), matrix_out(:, :)
    1986        96868 :       TYPE(preconditioner_p_type), ALLOCATABLE           :: preconditioners(:)
    1987        96868 :       TYPE(qs_ot_local_work_type), ALLOCATABLE           :: work(:)
    1988              : 
    1989        96868 :       IF (.NOT. qs_ot_local_channels(qs_ot_env)) RETURN
    1990         1734 :       IF (.NOT. qs_ot_env(1)%settings%ks .OR. qs_ot_env(1)%settings%ot_algorithm /= 'TOD') RETURN
    1991          792 :       IF (diag_lib_explicit .OR. diag_type == FM_DIAG_TYPE_DLAF) RETURN
    1992         2634 :       nchannels = COUNT(.NOT. qs_ot_env(:)%os_valid)
    1993          792 :       IF (nchannels == 0) RETURN
    1994          444 :       ALLOCATE (channels(nchannels))
    1995          148 :       j = 0
    1996          482 :       DO i = 1, SIZE(qs_ot_env)
    1997          334 :          IF (qs_ot_env(i)%os_valid) CYCLE
    1998          334 :          j = j + 1
    1999          482 :          channels(j) = i
    2000              :       END DO
    2001          148 :       nworkers = 1
    2002          148 : !$    nworkers = MIN(nchannels, omp_get_max_threads())
    2003            0 :       ALLOCATE (work(nworkers), preconditioners(nworkers), &
    2004         2368 :                 matrix_in(2, nworkers), matrix_out(2, nworkers))
    2005          296 :       DO j = 1, nworkers
    2006          296 :          CALL work(j)%gemm_ctx%create(LOCAL_GEMM_PU_GPU, timing=.FALSE.)
    2007              :       END DO
    2008          482 :       DO first = 1, nchannels, nworkers
    2009          334 :          nbatch = MIN(nworkers, nchannels - first + 1)
    2010          668 :          DO j = 1, nbatch
    2011          334 :             i = channels(first + j - 1)
    2012          334 :             preconditioners(j)%preconditioner => qs_ot_env(i)%preconditioner
    2013          334 :             matrix_in(1, j)%matrix => qs_ot_env(i)%matrix_sc0
    2014          334 :             matrix_in(2, j)%matrix => qs_ot_env(i)%matrix_sc0_im
    2015         1002 :             matrix_out(:, j) = matrix_in(:, j)
    2016          668 :             IF (ASSOCIATED(qs_ot_env(i)%preconditioner)) THEN
    2017          334 :                matrix_out(1, j)%matrix => qs_ot_env(i)%matrix_psc0
    2018          334 :                matrix_out(2, j)%matrix => qs_ot_env(i)%matrix_psc0_im
    2019              :             END IF
    2020              :          END DO
    2021              :          CALL apply_preconditioner_complex_batch(preconditioners(1:nbatch), &
    2022          334 :                                                  matrix_in(:, 1:nbatch), matrix_out(:, 1:nbatch), handled)
    2023          668 :          DO j = 1, nbatch
    2024          334 :             IF (.NOT. handled .AND. ASSOCIATED(preconditioners(j)%preconditioner)) THEN
    2025              :                CALL apply_preconditioner(preconditioners(j)%preconditioner, &
    2026              :                                          matrix_in(1, j)%matrix, matrix_in(2, j)%matrix, &
    2027            0 :                                          matrix_out(1, j)%matrix, matrix_out(2, j)%matrix)
    2028              :             END IF
    2029          334 :             CALL dbcsr_get_info(matrix_in(1, j)%matrix, nfullrows_total=n, nfullcols_total=k)
    2030          334 :             IF (ALLOCATED(work(j)%sc0)) THEN
    2031          186 :                IF (n /= work(j)%n .OR. k /= work(j)%k) THEN
    2032            0 :                   DEALLOCATE (work(j)%sc0, work(j)%target, work(j)%os, work(j)%r, &
    2033            0 :                               work(j)%buf1, work(j)%buf2, work(j)%vectors, work(j)%values)
    2034              :                END IF
    2035              :             END IF
    2036          334 :             IF (.NOT. ALLOCATED(work(j)%sc0)) THEN
    2037            0 :                ALLOCATE (work(j)%sc0(n, k, 2), work(j)%target(n, k, 2), work(j)%os(k, k, 2), &
    2038            0 :                          work(j)%r(k, k, 2), work(j)%buf1(k, k, 2), work(j)%buf2(k, k, 2), &
    2039         3256 :                          work(j)%vectors(k, k), work(j)%values(k))
    2040          148 :                work(j)%n = n
    2041          148 :                work(j)%k = k
    2042              :             END IF
    2043          334 :             CALL cp_cfm_local_workspace_prepare(work(j)%solver, k, work(j)%vectors)
    2044          334 :             CALL qs_ot_load_local_pair(matrix_in(1, j)%matrix, matrix_in(2, j)%matrix, work(j)%sc0)
    2045         1002 :             CALL qs_ot_load_local_pair(matrix_out(1, j)%matrix, matrix_out(2, j)%matrix, work(j)%target)
    2046              :          END DO
    2047              : !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) NUM_THREADS(nbatch) IF(nbatch > 1) &
    2048          334 : !$OMP SHARED(work,nbatch) PRIVATE(eval_threshold)
    2049              :          DO j = 1, nbatch
    2050              :             CALL qs_ot_complex_multiply_local('C', 'N', work(j)%sc0, work(j)%target, &
    2051              :                                               work(j)%os, work(j)%buf2(:, :, 1), work(j)%gemm_ctx)
    2052              :             work(j)%vectors(:, :) = CMPLX(work(j)%os(:, :, 1), work(j)%os(:, :, 2), KIND=dp)
    2053              :             CALL cp_cfm_heevd_local(work(j)%vectors, info=work(j)%info, ws=work(j)%solver)
    2054              :             IF (work(j)%info /= 0) CYCLE
    2055              :             eval_threshold = 100.0_dp*EPSILON(1.0_dp)*MAX(1.0_dp, MAXVAL(ABS(work(j)%solver%evals)))
    2056              :             IF (MINVAL(work(j)%solver%evals) <= eval_threshold) THEN
    2057              :                work(j)%info = -1
    2058              :                CYCLE
    2059              :             END IF
    2060              :             work(j)%values(:) = 1.0_dp/work(j)%solver%evals(:)
    2061              :             work(j)%r(:, :, 1) = REAL(work(j)%vectors, KIND=dp)
    2062              :             work(j)%r(:, :, 2) = AIMAG(work(j)%vectors)
    2063              :             CALL qs_ot_spectral_matrix_local(work(j), matrix=work(j)%os)
    2064              :          END DO
    2065              : !$OMP END PARALLEL DO
    2066          816 :          DO j = 1, nbatch
    2067          334 :             IF (work(j)%info /= 0) CYCLE
    2068          334 :             i = channels(first + j - 1)
    2069          334 :             CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_os, qs_ot_env(i)%matrix_os_im, work(j)%os)
    2070          668 :             qs_ot_env(i)%os_valid = .TRUE.
    2071              :          END DO
    2072              :       END DO
    2073          296 :       DO j = 1, nworkers
    2074          296 :          CALL work(j)%gemm_ctx%destroy()
    2075              :       END DO
    2076        97016 :    END SUBROUTINE qs_ot_prepare_complex_tangent_metric_batch
    2077              : 
    2078              : ! **************************************************************************************************
    2079              : !> \brief finite complex STRICT derivative, projected onto C0^H*S*X=0
    2080              : !> \param matrix_hc real part of H(k)*C(k)
    2081              : !> \param matrix_hc_im imaginary part of H(k)*C(k)
    2082              : !> \param qs_ot_env OT channel state
    2083              : ! **************************************************************************************************
    2084         2612 :    SUBROUTINE qs_ot_get_derivative_complex(matrix_hc, matrix_hc_im, qs_ot_env)
    2085              :       TYPE(dbcsr_type), POINTER                          :: matrix_hc, matrix_hc_im
    2086              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    2087              : 
    2088              :       CHARACTER(len=*), PARAMETER :: routineN = 'qs_ot_get_derivative_complex'
    2089              : 
    2090              :       INTEGER                                            :: handle
    2091              :       TYPE(dbcsr_distribution_type)                      :: dist
    2092              :       TYPE(dbcsr_type)                                   :: tmp_nk
    2093              :       TYPE(dbcsr_type), POINTER                          :: hc_work_im, hc_work_re
    2094              :       TYPE(dbcsr_type), TARGET                           :: hc_rot_im, hc_rot_re
    2095              : 
    2096         1306 :       CALL timeset(routineN, handle)
    2097              : 
    2098         1306 :       CPASSERT(qs_ot_env%has_complex_kpoint_state)
    2099         1306 :       CPASSERT(qs_ot_env%settings%ot_algorithm == 'TOD')
    2100              : 
    2101         1306 :       hc_work_re => matrix_hc
    2102         1306 :       hc_work_im => matrix_hc_im
    2103              : 
    2104         1306 :       IF (qs_ot_env%settings%do_rotation) THEN
    2105          634 :          CALL qs_ot_generate_rotation_complex(qs_ot_env)
    2106              : 
    2107              :          ! Reconstruct the unrotated finite STRICT orbitals Q(X).
    2108              :          CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_c0, qs_ot_env%matrix_c0_im, &
    2109              :                                      qs_ot_env%matrix_cosp, qs_ot_env%matrix_cosp_im, &
    2110              :                                      qs_ot_env%matrix_buf_nk, qs_ot_env%matrix_buf_nk_im, &
    2111          634 :                                      qs_ot_env%matrix_tmp_nk)
    2112              :          CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, &
    2113              :                                      qs_ot_env%matrix_sinp, qs_ot_env%matrix_sinp_im, &
    2114              :                                      qs_ot_env%matrix_gx, qs_ot_env%matrix_gx_im, &
    2115          634 :                                      qs_ot_env%matrix_tmp_nk)
    2116              :          CALL dbcsr_add(qs_ot_env%matrix_buf_nk, qs_ot_env%matrix_gx, &
    2117          634 :                         alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2118              :          CALL dbcsr_add(qs_ot_env%matrix_buf_nk_im, qs_ot_env%matrix_gx_im, &
    2119          634 :                         alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2120              : 
    2121              :          ! dF/dU = Q(X)^H*G_C for C=Q(X)*U.
    2122              :          CALL qs_ot_complex_multiply('C', 'N', &
    2123              :                                      qs_ot_env%matrix_buf_nk, qs_ot_env%matrix_buf_nk_im, &
    2124              :                                      matrix_hc, matrix_hc_im, &
    2125              :                                      qs_ot_env%rot_mat_dedu, qs_ot_env%rot_mat_dedu_im, &
    2126          634 :                                      qs_ot_env%matrix_buf1)
    2127          634 :          CALL qs_ot_rot_mat_derivative_complex(qs_ot_env)
    2128              : 
    2129              :          ! The STRICT coordinate sees G_Q=G_C*U^H.
    2130          634 :          CALL dbcsr_copy(hc_rot_re, matrix_hc, name="strict_hc_rot_re")
    2131          634 :          CALL dbcsr_copy(hc_rot_im, matrix_hc_im, name="strict_hc_rot_im")
    2132          634 :          CALL dbcsr_copy(tmp_nk, matrix_hc, name="strict_hc_rot_tmp")
    2133              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, matrix_hc, qs_ot_env%rot_mat_u, &
    2134          634 :                              0.0_dp, hc_rot_re)
    2135              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, matrix_hc_im, qs_ot_env%rot_mat_u_im, &
    2136          634 :                              0.0_dp, tmp_nk)
    2137          634 :          CALL dbcsr_add(hc_rot_re, tmp_nk, alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2138              : 
    2139              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, matrix_hc_im, qs_ot_env%rot_mat_u, &
    2140          634 :                              0.0_dp, hc_rot_im)
    2141              :          CALL dbcsr_multiply('N', 'T', 1.0_dp, matrix_hc, qs_ot_env%rot_mat_u_im, &
    2142          634 :                              0.0_dp, tmp_nk)
    2143          634 :          CALL dbcsr_add(hc_rot_im, tmp_nk, alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    2144          634 :          hc_work_re => hc_rot_re
    2145          634 :          hc_work_im => hc_rot_im
    2146              :       END IF
    2147              : 
    2148              :       ! Direct X contribution, H*C sinc(sqrt(P)).
    2149              :       CALL qs_ot_complex_multiply('N', 'N', hc_work_re, hc_work_im, &
    2150              :                                   qs_ot_env%matrix_sinp, qs_ot_env%matrix_sinp_im, &
    2151              :                                   qs_ot_env%matrix_gx, qs_ot_env%matrix_gx_im, &
    2152         1306 :                                   qs_ot_env%matrix_tmp_nk)
    2153              : 
    2154              :       CALL qs_ot_strict_frechet(hc_work_re, hc_work_im, qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, &
    2155         1306 :                                 qs_ot_env%matrix_sinp_b, qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf3_im, qs_ot_env)
    2156              :       CALL qs_ot_strict_frechet(hc_work_re, hc_work_im, qs_ot_env%matrix_c0, qs_ot_env%matrix_c0_im, &
    2157         1306 :                                 qs_ot_env%matrix_cosp_b, qs_ot_env%matrix_buf4, qs_ot_env%matrix_buf4_im, qs_ot_env)
    2158              :       CALL dbcsr_add(qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf4, &
    2159         1306 :                      alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2160              :       CALL dbcsr_add(qs_ot_env%matrix_buf3_im, qs_ot_env%matrix_buf4_im, &
    2161         1306 :                      alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2162              : 
    2163              :       ! Transform back and add the Hermitian adjoint generated by dP.
    2164              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_r, qs_ot_env%matrix_r_im, &
    2165              :                                   qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf3_im, &
    2166              :                                   qs_ot_env%matrix_buf1, qs_ot_env%matrix_buf1_im, &
    2167         1306 :                                   qs_ot_env%matrix_buf2)
    2168              :       CALL qs_ot_complex_multiply('N', 'C', qs_ot_env%matrix_buf1, qs_ot_env%matrix_buf1_im, &
    2169              :                                   qs_ot_env%matrix_r, qs_ot_env%matrix_r_im, &
    2170              :                                   qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf3_im, &
    2171         1306 :                                   qs_ot_env%matrix_buf2)
    2172         1306 :       CALL dbcsr_get_info(qs_ot_env%matrix_buf3, distribution=dist)
    2173              :       CALL dbcsr_transposed(qs_ot_env%matrix_buf4, qs_ot_env%matrix_buf3, &
    2174              :                             shallow_data_copy=.FALSE., use_distribution=dist, &
    2175         1306 :                             transpose_distribution=.FALSE.)
    2176              :       CALL dbcsr_transposed(qs_ot_env%matrix_buf4_im, qs_ot_env%matrix_buf3_im, &
    2177              :                             shallow_data_copy=.FALSE., use_distribution=dist, &
    2178         1306 :                             transpose_distribution=.FALSE.)
    2179              :       CALL dbcsr_add(qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf4, &
    2180         1306 :                      alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2181              :       CALL dbcsr_add(qs_ot_env%matrix_buf3_im, qs_ot_env%matrix_buf4_im, &
    2182         1306 :                      alpha_scalar=1.0_dp, beta_scalar=-1.0_dp)
    2183              : 
    2184              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_sx, qs_ot_env%matrix_sx_im, &
    2185              :                                   qs_ot_env%matrix_buf3, qs_ot_env%matrix_buf3_im, &
    2186              :                                   qs_ot_env%matrix_buf_nk, qs_ot_env%matrix_buf_nk_im, &
    2187         1306 :                                   qs_ot_env%matrix_tmp_nk)
    2188              :       CALL dbcsr_add(qs_ot_env%matrix_gx, qs_ot_env%matrix_buf_nk, &
    2189         1306 :                      alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2190              :       CALL dbcsr_add(qs_ot_env%matrix_gx_im, qs_ot_env%matrix_buf_nk_im, &
    2191         1306 :                      alpha_scalar=1.0_dp, beta_scalar=1.0_dp)
    2192              : 
    2193         1306 :       CALL qs_ot_project_gradient(qs_ot_env, qs_ot_env%matrix_gx, qs_ot_env%matrix_gx_im)
    2194              : 
    2195         1306 :       IF (qs_ot_env%settings%do_rotation) THEN
    2196          634 :          CALL dbcsr_release(hc_rot_re)
    2197          634 :          CALL dbcsr_release(hc_rot_im)
    2198          634 :          CALL dbcsr_release(tmp_nk)
    2199              :       END IF
    2200              : 
    2201         1306 :       CALL timestop(handle)
    2202              : 
    2203         1306 :    END SUBROUTINE qs_ot_get_derivative_complex
    2204              : 
    2205              : ! **************************************************************************************************
    2206              : !> \brief Whether all channels can use the single-rank STRICT/IRAC kernels.
    2207              : !> \param qs_ot_env local OT channels
    2208              : !> \return true when every channel is eligible
    2209              : ! **************************************************************************************************
    2210        99940 :    LOGICAL FUNCTION qs_ot_local_channels(qs_ot_env) RESULT(local)
    2211              :       TYPE(qs_ot_type), INTENT(IN)                       :: qs_ot_env(:)
    2212              : 
    2213              :       INTEGER                                            :: i
    2214              : 
    2215        99940 :       local = .FALSE.
    2216        99940 :       IF (SIZE(qs_ot_env) == 0) RETURN
    2217       109574 :       DO i = 1, SIZE(qs_ot_env)
    2218       104768 :          IF (.NOT. qs_ot_env(i)%has_complex_kpoint_state) RETURN
    2219              :          SELECT CASE (qs_ot_env(i)%settings%ot_algorithm)
    2220              :          CASE ('TOD', 'REF')
    2221              :          CASE DEFAULT
    2222        11518 :             RETURN
    2223              :          END SELECT
    2224        11518 :          IF (qs_ot_env(i)%settings%ot_algorithm /= qs_ot_env(1)%settings%ot_algorithm) RETURN
    2225        16324 :          IF (qs_ot_env(i)%para_env%num_pe /= 1) RETURN
    2226              :       END DO
    2227        99940 :       local = .TRUE.
    2228              :    END FUNCTION qs_ot_local_channels
    2229              : 
    2230              : ! **************************************************************************************************
    2231              : !> \brief Rebuild single-rank STRICT/IRAC orbitals in bounded batches using cached overlaps.
    2232              : !>        DBCSR packing and writeback are outside OpenMP; workers only use dense data and private scratch.
    2233              : !> \param qs_ot_env local OT channels
    2234              : !> \param matrix_s pointers to cached real/imaginary overlaps, indexed by component and channel
    2235              : !> \param matrix_c pointers to physical orbitals; only their active columns are updated
    2236              : !> \param initialize build C0/SC0 from physical orbitals without modifying the physical state
    2237              : ! **************************************************************************************************
    2238         1816 :    SUBROUTINE qs_ot_get_orbitals_local_batch(qs_ot_env, matrix_s, matrix_c, initialize)
    2239              :       TYPE(qs_ot_type), INTENT(INOUT)                    :: qs_ot_env(:)
    2240              :       TYPE(cp_fm_p_type), INTENT(IN)                     :: matrix_s(:, :)
    2241              :       TYPE(cp_cfm_p_type), INTENT(IN)                    :: matrix_c(:)
    2242              :       LOGICAL, INTENT(IN), OPTIONAL                      :: initialize
    2243              : 
    2244              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'qs_ot_get_orbitals_local_batch'
    2245              : 
    2246              :       INTEGER                                            :: first, handle, i, j, k, n, nbatch, &
    2247              :                                                             nchannels, nworkers
    2248              :       LOGICAL                                            :: initial, ref, update
    2249         1816 :       TYPE(qs_ot_local_work_type), ALLOCATABLE           :: work(:)
    2250              : 
    2251         1816 :       initial = .FALSE.
    2252         1816 :       IF (PRESENT(initialize)) initial = initialize
    2253         1816 :       CPASSERT(qs_ot_local_channels(qs_ot_env))
    2254         1816 :       ref = qs_ot_env(1)%settings%ot_algorithm == 'REF'
    2255         1816 :       update = .FALSE.
    2256         1816 :       IF (ref) update = qs_ot_ref_update(qs_ot_env(1))
    2257         1816 :       CALL timeset(routineN, handle)
    2258         1816 :       nchannels = SIZE(qs_ot_env)
    2259         1816 :       nworkers = 1
    2260         1816 : !$    nworkers = MIN(nchannels, omp_get_max_threads())
    2261         7264 :       ALLOCATE (work(nworkers))
    2262         3632 :       DO j = 1, nworkers
    2263         3632 :          CALL work(j)%gemm_ctx%create(LOCAL_GEMM_PU_GPU, timing=.FALSE.)
    2264              :       END DO
    2265         5446 :       DO first = 1, nchannels, nworkers
    2266         3630 :          nbatch = MIN(nworkers, nchannels - first + 1)
    2267         7260 :          DO j = 1, nbatch
    2268         3630 :             i = first + j - 1
    2269         3630 :             CALL dbcsr_get_info(qs_ot_env(i)%matrix_x, nfullrows_total=n, nfullcols_total=k)
    2270         3630 :             CALL qs_ot_allocate_local_work(work(j), n, k)
    2271         3630 :             work(j)%do_rotation = qs_ot_env(i)%settings%do_rotation .AND. .NOT. initial
    2272         3630 :             IF (.NOT. initial .OR. ref) CALL qs_ot_prepare_local_spectrum(work(j))
    2273         3630 :             IF (work(j)%do_rotation) THEN
    2274          951 :                CALL qs_ot_load_local_pair(qs_ot_env(i)%rot_mat_x, qs_ot_env(i)%rot_mat_x_im, work(j)%rot)
    2275              :             END IF
    2276        10890 :             IF (initial) THEN
    2277        77555 :                work(j)%x(:, :, 1) = REAL(matrix_c(i)%matrix%local_data(1:n, 1:k), KIND=dp)
    2278        77555 :                work(j)%x(:, :, 2) = AIMAG(matrix_c(i)%matrix%local_data(1:n, 1:k))
    2279              :             ELSE
    2280         3231 :                CALL qs_ot_load_local_pair(qs_ot_env(i)%matrix_x, qs_ot_env(i)%matrix_x_im, work(j)%x)
    2281         3231 :                IF (.NOT. ref) THEN
    2282         1842 :                   CALL qs_ot_load_local_pair(qs_ot_env(i)%matrix_c0, qs_ot_env(i)%matrix_c0_im, work(j)%c0)
    2283              :                END IF
    2284              :             END IF
    2285              :          END DO
    2286              : !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) NUM_THREADS(nbatch) IF(nbatch > 1) &
    2287         3630 : !$OMP SHARED(work,matrix_s,matrix_c,nbatch,first,ref,UPDATE,initial) PRIVATE(i,n,k)
    2288              :          DO j = 1, nbatch
    2289              :             i = first + j - 1
    2290              :             IF (work(j)%do_rotation) THEN
    2291              :                CALL qs_ot_generate_rotation_local(work(j))
    2292              :                IF (work(j)%info /= 0) CYCLE
    2293              :             END IF
    2294              :             CALL qs_ot_orbitals_local(work(j), matrix_s(1, i)%matrix%local_data, &
    2295              :                                       matrix_s(2, i)%matrix%local_data, ref, update, initial)
    2296              :             IF (work(j)%info /= 0) CYCLE
    2297              :             IF (initial) CYCLE
    2298              :             IF (work(j)%do_rotation) THEN
    2299              :                ! IRAC retraction updates X to Q; rotate only the physical orbitals.
    2300              :                CALL qs_ot_complex_multiply_local('N', 'N', work(j)%out, work(j)%rot, &
    2301              :                                                  work(j)%buf_nk, work(j)%tmp, work(j)%gemm_ctx)
    2302              :                work(j)%out(:, :, :) = work(j)%buf_nk(:, :, :)
    2303              :             END IF
    2304              :             n = work(j)%n
    2305              :             k = work(j)%k
    2306              :             matrix_c(i)%matrix%local_data(1:n, 1:k) = CMPLX(work(j)%out(:, :, 1), work(j)%out(:, :, 2), KIND=dp)
    2307              :          END DO
    2308              : !$OMP END PARALLEL DO
    2309         9076 :          DO j = 1, nbatch
    2310         3630 :             IF (work(j)%info == -1) CPABORT('Complex REF overlap is not positive definite')
    2311         3630 :             IF (work(j)%info /= 0) CPABORT('Local complex OT diagonalization failed')
    2312         3630 :             i = first + j - 1
    2313         3630 :             IF (work(j)%do_rotation) THEN
    2314          951 :                CALL qs_ot_store_local_pair(qs_ot_env(i)%rot_mat_u, qs_ot_env(i)%rot_mat_u_im, work(j)%rot)
    2315              :             END IF
    2316         3630 :             IF (initial) THEN
    2317          399 :                CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_c0, qs_ot_env(i)%matrix_c0_im, work(j)%out)
    2318          399 :                CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_sc0, qs_ot_env(i)%matrix_sc0_im, work(j)%buf_nk)
    2319          399 :                IF (.NOT. ref) CYCLE
    2320              :             END IF
    2321         3296 :             CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_sx, qs_ot_env(i)%matrix_sx_im, work(j)%sx)
    2322         3296 :             IF (ref) THEN
    2323              :                CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_ref_inv_sqrt, &
    2324         1454 :                                            qs_ot_env(i)%matrix_ref_inv_sqrt_im, work(j)%sinp)
    2325         1454 :                IF (update .OR. initial) CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_x, qs_ot_env(i)%matrix_x_im, work(j)%x)
    2326              :                CYCLE
    2327              :             END IF
    2328         1842 :             CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_p, qs_ot_env(i)%matrix_p_im, work(j)%buf3)
    2329         1842 :             CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_r, qs_ot_env(i)%matrix_r_im, work(j)%r)
    2330         1842 :             CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_cosp, qs_ot_env(i)%matrix_cosp_im, work(j)%buf4)
    2331         1842 :             CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_sinp, qs_ot_env(i)%matrix_sinp_im, work(j)%sinp)
    2332         1842 :             CALL copy_local_to_dbcsr(qs_ot_env(i)%matrix_cosp_b, work(j)%cosp_b)
    2333         1842 :             CALL copy_local_to_dbcsr(qs_ot_env(i)%matrix_sinp_b, work(j)%sinp_b)
    2334        11590 :             qs_ot_env(i)%evals(:) = work(j)%solver%evals(:)
    2335         5806 :             qs_ot_env(i)%do_taylor = .FALSE.
    2336              :          END DO
    2337              :       END DO
    2338         3632 :       DO j = 1, nworkers
    2339         3632 :          CALL work(j)%gemm_ctx%destroy()
    2340              :       END DO
    2341         1816 :       CALL timestop(handle)
    2342         5448 :    END SUBROUTINE qs_ot_get_orbitals_local_batch
    2343              : 
    2344              : ! **************************************************************************************************
    2345              : !> \brief Evaluate S*X and reconstruct physical STRICT or IRAC orbitals.
    2346              : !> \param w private storage and status; buf3 retains P and buf4 retains cos(sqrt(P)) for writeback
    2347              : !> \param overlap_re cached real overlap, including any leading-dimension padding
    2348              : !> \param overlap_im cached imaginary overlap
    2349              : !> \param ref select IRAC instead of STRICT
    2350              : !> \param update accept the IRAC coordinate at the existing minimizer update point
    2351              : !> \param initialize retain SC0 and use the analytic zero-coordinate STRICT state
    2352              : ! **************************************************************************************************
    2353         3630 :    SUBROUTINE qs_ot_orbitals_local(w, overlap_re, overlap_im, ref, update, initialize)
    2354              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2355              :       REAL(dp), CONTIGUOUS, INTENT(IN)                   :: overlap_re(:, :), overlap_im(:, :)
    2356              :       LOGICAL, INTENT(IN)                                :: ref, update
    2357              :       LOGICAL, INTENT(IN), OPTIONAL                      :: initialize
    2358              : 
    2359              :       INTEGER                                            :: i
    2360              : 
    2361              :       CALL qs_ot_complex_multiply_local('N', 'N', overlap_re, overlap_im, w%x(:, :, 1), w%x(:, :, 2), &
    2362         3630 :                                         w%sx(:, :, 1), w%sx(:, :, 2), w%tmp, w%gemm_ctx)
    2363         3630 :       IF (PRESENT(initialize)) THEN
    2364         3630 :          IF (initialize) THEN
    2365              :             ! Keep the incoming SC0, as in the distributed initializer. IRAC may
    2366              :             ! subsequently normalize C0 and accept a different X/SX pair.
    2367       155509 :             w%buf_nk(:, :, :) = w%sx(:, :, :)
    2368          399 :             IF (.NOT. ref) THEN
    2369        82202 :                w%out(:, :, :) = w%x(:, :, :)
    2370          334 :                w%info = 0
    2371          334 :                RETURN
    2372              :             END IF
    2373              :          END IF
    2374              :       END IF
    2375         3296 :       CALL qs_ot_complex_multiply_local('C', 'N', w%x, w%sx, w%buf3, w%buf2(:, :, 1), w%gemm_ctx)
    2376       311196 :       w%vectors(:, :) = CMPLX(w%buf3(:, :, 1), w%buf3(:, :, 2), KIND=dp)
    2377         3296 :       CALL cp_cfm_heevd_local(w%vectors, info=w%info, ws=w%solver)
    2378         3296 :       IF (w%info /= 0) RETURN
    2379       311196 :       w%r(:, :, 1) = REAL(w%vectors, KIND=dp)
    2380       311196 :       w%r(:, :, 2) = AIMAG(w%vectors)
    2381         3296 :       IF (ref) THEN
    2382        15780 :          IF (ANY(w%solver%evals <= EPSILON(1.0_dp))) THEN
    2383            0 :             w%info = -1
    2384            0 :             RETURN
    2385              :          END IF
    2386        15780 :          w%values(:) = 1.0_dp/SQRT(w%solver%evals(:))
    2387         1454 :          CALL qs_ot_spectral_matrix_local(w, matrix=w%sinp)
    2388         1454 :          CALL qs_ot_complex_multiply_local('N', 'N', w%x, w%sinp, w%out, w%tmp, w%gemm_ctx)
    2389         1454 :          IF (update) THEN
    2390       792496 :             w%x(:, :, :) = w%out(:, :, :)
    2391       192320 :             w%sinp(:, :, :) = 0.0_dp
    2392         7494 :             DO i = 1, w%k
    2393         7494 :                w%sinp(i, i, 1) = 1.0_dp
    2394              :             END DO
    2395              :             CALL qs_ot_complex_multiply_local('N', 'N', overlap_re, overlap_im, w%x(:, :, 1), w%x(:, :, 2), &
    2396          712 :                                               w%sx(:, :, 1), w%sx(:, :, 2), w%tmp, w%gemm_ctx)
    2397              :          END IF
    2398         1454 :          RETURN
    2399              :       END IF
    2400        11590 :       w%solver%evals(:) = MAX(0.0_dp, w%solver%evals(:))
    2401         1842 :       CALL qs_ot_spectral_matrix_local(w, .TRUE., w%buf4)
    2402         1842 :       CALL qs_ot_spectral_matrix_local(w, .FALSE., w%sinp)
    2403        11590 :       w%values(:) = SQRT(w%solver%evals(:))
    2404         1842 :       CALL qs_ot_spectral_kernel(w%values, w%values, w%cosp_b, .TRUE.)
    2405         1842 :       CALL qs_ot_spectral_kernel(w%values, w%values, w%sinp_b, .FALSE.)
    2406         1842 :       CALL qs_ot_complex_multiply_local('N', 'N', w%c0, w%buf4, w%out, w%tmp, w%gemm_ctx)
    2407         1842 :       CALL qs_ot_complex_multiply_local('N', 'N', w%x, w%sinp, w%buf_nk, w%tmp, w%gemm_ctx)
    2408       622774 :       w%out(:, :, :) = w%out(:, :, :) + w%buf_nk(:, :, :)
    2409              :    END SUBROUTINE qs_ot_orbitals_local
    2410              : 
    2411              : ! **************************************************************************************************
    2412              : !> \brief Reconstruct one spectral matrix function from the local eigenvectors.
    2413              : !> \param w private eigenvectors, eigenvalues and scratch
    2414              : !> \param cosine select cos/sinc(sqrt(P)); if absent, use the supplied spectral values
    2415              : !> \param matrix dense real/imaginary result
    2416              : ! **************************************************************************************************
    2417         5472 :    SUBROUTINE qs_ot_spectral_matrix_local(w, cosine, matrix)
    2418              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2419              :       LOGICAL, INTENT(IN), OPTIONAL                      :: cosine
    2420              :       REAL(dp), CONTIGUOUS, INTENT(INOUT)                :: matrix(:, :, :)
    2421              : 
    2422              :       INTEGER                                            :: i
    2423              : 
    2424         5472 :       IF (PRESENT(cosine)) CALL qs_ot_spectral_values(w%solver%evals, w%values, cosine)
    2425        40778 :       DO i = 1, w%k
    2426       417244 :          w%buf1(:, i, 1) = w%r(:, i, 1)*w%values(i)
    2427       422716 :          w%buf1(:, i, 2) = w%r(:, i, 2)*w%values(i)
    2428              :       END DO
    2429         5472 :       CALL qs_ot_complex_multiply_local('N', 'C', w%buf1, w%r, matrix, w%buf2(:, :, 1), w%gemm_ctx)
    2430         5472 :    END SUBROUTINE qs_ot_spectral_matrix_local
    2431              : 
    2432              : ! **************************************************************************************************
    2433              : !> \brief Allocate common dense OT storage only when a worker's matrix dimensions change.
    2434              : !> \param w worker storage
    2435              : !> \param n AO count
    2436              : !> \param k active MO count
    2437              : ! **************************************************************************************************
    2438         6301 :    SUBROUTINE qs_ot_allocate_local_work(w, n, k)
    2439              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2440              :       INTEGER, INTENT(IN)                                :: n, k
    2441              : 
    2442         6301 :       IF (ALLOCATED(w%x)) THEN
    2443         3229 :          IF (n == w%n .AND. k == w%k) RETURN
    2444            0 :          DEALLOCATE (w%x, w%sx, w%c0, w%out, w%r, w%sinp, w%buf1, w%buf2, w%buf3, w%buf4, &
    2445          162 :                      w%buf_nk, w%sinp_b, w%cosp_b, w%tmp)
    2446              :       END IF
    2447            0 :       ALLOCATE (w%x(n, k, 2), w%sx(n, k, 2), w%c0(n, k, 2), w%out(n, k, 2), &
    2448            0 :                 w%r(k, k, 2), w%sinp(k, k, 2), w%buf1(k, k, 2), w%buf2(k, k, 2), &
    2449            0 :                 w%buf3(k, k, 2), w%buf4(k, k, 2), w%buf_nk(n, k, 2), &
    2450       113190 :                 w%sinp_b(k, k), w%cosp_b(k, k), w%tmp(n, k))
    2451         3234 :       w%n = n
    2452         3234 :       w%k = k
    2453              :    END SUBROUTINE qs_ot_allocate_local_work
    2454              : 
    2455              : ! **************************************************************************************************
    2456              : !> \brief Prepare a worker's orbital and optional rotation eigensolvers outside OpenMP.
    2457              : !> \param w worker with known matrix dimensions
    2458              : ! **************************************************************************************************
    2459         3817 :    SUBROUTINE qs_ot_prepare_local_spectrum(w)
    2460              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2461              : 
    2462         3817 :       IF (ALLOCATED(w%vectors)) THEN
    2463         1791 :          IF (SIZE(w%vectors, 1) /= w%k) DEALLOCATE (w%vectors, w%values)
    2464              :       END IF
    2465        14487 :       IF (.NOT. ALLOCATED(w%vectors)) ALLOCATE (w%vectors(w%k, w%k), w%values(w%k))
    2466         3817 :       CALL cp_cfm_local_workspace_prepare(w%solver, w%k, w%vectors)
    2467         3817 :       IF (w%do_rotation) THEN
    2468         1472 :          CALL qs_ot_prepare_rotation_work(w%rotation, w%k)
    2469         1472 :          IF (ALLOCATED(w%rot)) THEN
    2470          394 :             IF (SIZE(w%rot, 1) /= w%k) DEALLOCATE (w%rot, w%rot_g)
    2471              :          END IF
    2472         7940 :          IF (.NOT. ALLOCATED(w%rot)) ALLOCATE (w%rot(w%k, w%k, 2), w%rot_g(w%k, w%k, 2))
    2473              :       END IF
    2474         3817 :    END SUBROUTINE qs_ot_prepare_local_spectrum
    2475              : 
    2476              : ! **************************************************************************************************
    2477              : !> \brief Evaluate a worker's rotation and retain its spectrum for the gradient pullback.
    2478              : !> \param w prepared storage; rot contains A on entry and U on return
    2479              : ! **************************************************************************************************
    2480         1472 :    SUBROUTINE qs_ot_generate_rotation_local(w)
    2481              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2482              : 
    2483       228944 :       w%vectors(:, :) = CMPLX(w%rot(:, :, 1), w%rot(:, :, 2), KIND=dp)
    2484         1472 :       CALL qs_ot_rotation_state_local(w%vectors, w%rotation, w%gemm_ctx)
    2485         1472 :       w%info = w%rotation%info
    2486         1472 :       IF (w%info /= 0) RETURN
    2487       228944 :       w%rot(:, :, 1) = REAL(w%rotation%u, KIND=dp)
    2488       228944 :       w%rot(:, :, 2) = AIMAG(w%rotation%u)
    2489              :    END SUBROUTINE qs_ot_generate_rotation_local
    2490              : 
    2491              : ! **************************************************************************************************
    2492              : !> \brief Write a dense complex pair back to two DBCSR matrices outside OpenMP.
    2493              : !> \param matrix_re real DBCSR output
    2494              : !> \param matrix_im imaginary DBCSR output
    2495              : !> \param dense dense real/imaginary pair
    2496              : ! **************************************************************************************************
    2497        18783 :    SUBROUTINE qs_ot_store_local_pair(matrix_re, matrix_im, dense)
    2498              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix_re, matrix_im
    2499              :       REAL(dp), INTENT(IN)                               :: dense(:, :, :)
    2500              : 
    2501        18783 :       CALL copy_local_to_dbcsr(matrix_re, dense(:, :, 1))
    2502        18783 :       CALL copy_local_to_dbcsr(matrix_im, dense(:, :, 2))
    2503        18783 :    END SUBROUTINE qs_ot_store_local_pair
    2504              : 
    2505              : ! **************************************************************************************************
    2506              : !> \brief Evaluate independent, single-rank STRICT/IRAC gradients in bounded OpenMP batches.
    2507              : !>        DBCSR packing, filtering and metric preparation stay outside the worker region.
    2508              : !> \param qs_ot_env local OT channels
    2509              : !> \param matrix_hc real orbital gradients
    2510              : !> \param matrix_hc_im imaginary orbital gradients
    2511              : !> \param matrix_hc_physical optional physical gradients for occupation preconditioning
    2512              : !> \param matrix_hc_physical_im imaginary physical gradients
    2513              : ! **************************************************************************************************
    2514         2512 :    SUBROUTINE qs_ot_get_derivative_local_batch(qs_ot_env, matrix_hc, matrix_hc_im, &
    2515         1256 :                                                matrix_hc_physical, matrix_hc_physical_im)
    2516              :       TYPE(qs_ot_type), INTENT(INOUT)                    :: qs_ot_env(:)
    2517              :       TYPE(dbcsr_p_type), INTENT(IN)                     :: matrix_hc(:), matrix_hc_im(:)
    2518              :       TYPE(dbcsr_p_type), INTENT(IN), OPTIONAL           :: matrix_hc_physical(:), &
    2519              :                                                             matrix_hc_physical_im(:)
    2520              : 
    2521              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'qs_ot_get_derivative_local_batch'
    2522              : 
    2523              :       INTEGER                                            :: first, handle, i, j, nbatch, nchannels, &
    2524              :                                                             npasses, nworkers, pass
    2525              :       LOGICAL                                            :: ref
    2526         1256 :       TYPE(qs_ot_local_work_type), ALLOCATABLE           :: work(:)
    2527              : 
    2528         1256 :       CPASSERT(qs_ot_local_channels(qs_ot_env))
    2529         1256 :       ref = qs_ot_env(1)%settings%ot_algorithm == 'REF'
    2530         1256 :       npasses = 1
    2531         1256 :       IF (PRESENT(matrix_hc_physical)) THEN
    2532          116 :          CPASSERT(PRESENT(matrix_hc_physical_im))
    2533              :          npasses = 2
    2534              :       END IF
    2535         1256 :       nchannels = SIZE(qs_ot_env)
    2536              : 
    2537         1256 :       CALL timeset(routineN, handle)
    2538         1256 :       nworkers = 1
    2539         1256 : !$    nworkers = MIN(nchannels, omp_get_max_threads())
    2540         5024 :       ALLOCATE (work(nworkers))
    2541         2512 :       DO j = 1, nworkers
    2542         2512 :          CALL work(j)%gemm_ctx%create(LOCAL_GEMM_PU_GPU, timing=.FALSE.)
    2543              :       END DO
    2544         3927 :       DO first = 1, nchannels, nworkers
    2545         2671 :          nbatch = MIN(nworkers, nchannels - first + 1)
    2546         5342 :          DO j = 1, nbatch
    2547         2671 :             i = first + j - 1
    2548         2671 :             IF (.NOT. ref) CALL qs_ot_prepare_complex_tangent_metric(qs_ot_env(i))
    2549         2671 :             CALL qs_ot_load_local_work(work(j), qs_ot_env(i), matrix_hc(i)%matrix, matrix_hc_im(i)%matrix)
    2550         8013 :             work(j)%filtered = ref .AND. qs_ot_env(i)%settings%eps_irac_filter_matrix > 0.0_dp
    2551              :          END DO
    2552         6714 :          DO pass = 1, npasses
    2553         2787 :             IF (pass == 2) THEN
    2554          232 :                DO j = 1, nbatch
    2555          116 :                   i = first + j - 1
    2556          232 :                   CALL qs_ot_load_local_pair(matrix_hc_physical(i)%matrix, matrix_hc_physical_im(i)%matrix, work(j)%hc)
    2557              :                END DO
    2558              :             END IF
    2559              : !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) NUM_THREADS(nbatch) IF(nbatch > 1) &
    2560         2787 : !$OMP SHARED(work,nbatch,ref,pass,npasses)
    2561              :             DO j = 1, nbatch
    2562              :                IF (ref) THEN
    2563              :                   CALL qs_ot_complex_multiply_local('N', 'N', work(j)%x, work(j)%sinp, &
    2564              :                                                     work(j)%c0, work(j)%tmp, work(j)%gemm_ctx)
    2565              :                END IF
    2566              :                IF (work(j)%do_rotation) THEN
    2567              :                   CALL qs_ot_rotation_gradient_local(work(j), ref, prepare=pass == 1, physical=pass == npasses)
    2568              :                   IF (work(j)%info /= 0) CYCLE
    2569              :                END IF
    2570              :                IF (ref) THEN
    2571              :                   CALL qs_ot_complex_multiply_local('C', 'N', work(j)%c0, work(j)%hc, &
    2572              :                                                     work(j)%buf3, work(j)%buf2(:, :, 1), work(j)%gemm_ctx)
    2573              :                   IF (.NOT. work(j)%filtered) CALL qs_ot_ref_gradient_local(work(j))
    2574              :                ELSE
    2575              :                   CALL qs_ot_strict_gradient_local(work(j))
    2576              :                END IF
    2577              :             END DO
    2578              : !$OMP END PARALLEL DO
    2579         5574 :             IF (ANY(work(1:nbatch)%info == -1)) THEN
    2580            0 :                CPABORT('Complex REF inverse square root is not positive definite')
    2581              :             END IF
    2582         5574 :             IF (ANY(work(1:nbatch)%info /= 0)) THEN
    2583            0 :                CPABORT('Local complex OT diagonalization failed')
    2584              :             END IF
    2585         5574 :             IF (ANY(work(1:nbatch)%filtered)) THEN
    2586              :                ! Keep the existing block filter, including independent real/imaginary sparsity.
    2587            0 :                DO j = 1, nbatch
    2588            0 :                   IF (.NOT. work(j)%filtered) CYCLE
    2589            0 :                   i = first + j - 1
    2590            0 :                   CALL qs_ot_store_local_pair(qs_ot_env(i)%buf1_k_k_sym, qs_ot_env(i)%buf2_k_k_sym, work(j)%buf3)
    2591            0 :                   CALL dbcsr_filter(qs_ot_env(i)%buf1_k_k_sym, qs_ot_env(i)%settings%eps_irac_filter_matrix)
    2592            0 :                   CALL dbcsr_filter(qs_ot_env(i)%buf2_k_k_sym, qs_ot_env(i)%settings%eps_irac_filter_matrix)
    2593            0 :                   CALL qs_ot_load_local_pair(qs_ot_env(i)%buf1_k_k_sym, qs_ot_env(i)%buf2_k_k_sym, work(j)%buf3)
    2594              :                END DO
    2595            0 : !$OMP PARALLEL DO DEFAULT(NONE) SCHEDULE(STATIC) NUM_THREADS(nbatch) IF(nbatch > 1) SHARED(work,nbatch)
    2596              :                DO j = 1, nbatch
    2597              :                   IF (work(j)%filtered) CALL qs_ot_ref_gradient_local(work(j))
    2598              :                END DO
    2599              : !$OMP END PARALLEL DO
    2600              :             END IF
    2601         8245 :             DO j = 1, nbatch
    2602         2787 :                i = first + j - 1
    2603         2787 :                IF (work(j)%do_rotation .AND. pass == npasses) THEN
    2604          521 :                   CALL qs_ot_store_local_pair(qs_ot_env(i)%rot_mat_u, qs_ot_env(i)%rot_mat_u_im, work(j)%rot)
    2605          521 :                   CALL qs_ot_store_local_pair(qs_ot_env(i)%rot_mat_gx, qs_ot_env(i)%rot_mat_gx_im, work(j)%rot_g)
    2606              :                END IF
    2607         2787 :                CALL qs_ot_store_local_pair(qs_ot_env(i)%matrix_gx, qs_ot_env(i)%matrix_gx_im, work(j)%out)
    2608         2787 :                IF (work(j)%filtered) THEN
    2609            0 :                   CALL dbcsr_filter(qs_ot_env(i)%matrix_gx, qs_ot_env(i)%settings%eps_irac_filter_matrix)
    2610            0 :                   CALL dbcsr_filter(qs_ot_env(i)%matrix_gx_im, qs_ot_env(i)%settings%eps_irac_filter_matrix)
    2611              :                END IF
    2612         5574 :                IF (pass < npasses) THEN
    2613          116 :                   CALL dbcsr_copy(qs_ot_env(i)%matrix_preconditioned_gx, qs_ot_env(i)%matrix_gx)
    2614          116 :                   CALL dbcsr_copy(qs_ot_env(i)%matrix_preconditioned_gx_im, qs_ot_env(i)%matrix_gx_im)
    2615              :                END IF
    2616              :             END DO
    2617              :          END DO
    2618              :       END DO
    2619         2512 :       DO j = 1, nworkers
    2620         2512 :          CALL work(j)%gemm_ctx%destroy()
    2621              :       END DO
    2622         1256 :       CALL timestop(handle)
    2623         3768 :    END SUBROUTINE qs_ot_get_derivative_local_batch
    2624              : 
    2625              : ! **************************************************************************************************
    2626              : !> \brief Pull back through rotation, sharing spectra across occupation-preconditioner passes.
    2627              : !> \param w worker with G_C in hc; hc receives G_Q and rot_g receives the rotation gradient
    2628              : !> \param ref select IRAC with Q already in c0 instead of STRICT with the reference C0 in c0
    2629              : !> \param prepare compute the rotation and, for IRAC, polar spectra on the first pass
    2630              : !> \param physical compute the rotation gradient only from the physical covector
    2631              : ! **************************************************************************************************
    2632          637 :    SUBROUTINE qs_ot_rotation_gradient_local(w, ref, prepare, physical)
    2633              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2634              :       LOGICAL, INTENT(IN)                                :: ref, prepare, physical
    2635              : 
    2636          637 :       IF (prepare) THEN
    2637          521 :          CALL qs_ot_generate_rotation_local(w)
    2638          521 :          IF (w%info /= 0) RETURN
    2639          521 :          IF (ref) THEN
    2640        46497 :             w%vectors(:, :) = CMPLX(w%sinp(:, :, 1), w%sinp(:, :, 2), KIND=dp)
    2641          431 :             CALL cp_cfm_heevd_local(w%vectors, info=w%info, ws=w%solver)
    2642          431 :             IF (w%info /= 0) RETURN
    2643         4165 :             IF (ANY(w%solver%evals <= EPSILON(1.0_dp))) THEN
    2644            0 :                w%info = -1
    2645            0 :                RETURN
    2646              :             END IF
    2647        46497 :             w%r(:, :, 1) = REAL(w%vectors, KIND=dp)
    2648        46497 :             w%r(:, :, 2) = AIMAG(w%vectors)
    2649              :          END IF
    2650              :       END IF
    2651          637 :       IF (physical) THEN
    2652          521 :          IF (ref) THEN
    2653          431 :             CALL qs_ot_complex_multiply_local('C', 'N', w%c0, w%hc, w%buf3, w%buf2(:, :, 1), w%gemm_ctx)
    2654              :          ELSE
    2655              :             ! Q(X) = C0*cos(sqrt(P)) + X*sinc(sqrt(P)), preserving C0 and the STRICT kernels.
    2656           90 :             CALL qs_ot_complex_multiply_local('N', 'N', w%c0, w%cosp, w%out, w%tmp, w%gemm_ctx)
    2657           90 :             CALL qs_ot_complex_multiply_local('N', 'N', w%x, w%sinp, w%buf_nk, w%tmp, w%gemm_ctx)
    2658       153886 :             w%out(:, :, :) = w%out(:, :, :) + w%buf_nk(:, :, :)
    2659           90 :             CALL qs_ot_complex_multiply_local('C', 'N', w%out, w%hc, w%buf3, w%buf2(:, :, 1), w%gemm_ctx)
    2660              :          END IF
    2661        80063 :          w%rotation%work(:, :) = CMPLX(w%buf3(:, :, 1), w%buf3(:, :, 2), KIND=dp)
    2662          521 :          CALL qs_ot_rotation_pullback_local(w%rotation, w%gemm_ctx, w%rot_g(:, :, 1), w%rot_g(:, :, 2))
    2663              :       END IF
    2664          637 :       CALL qs_ot_complex_multiply_local('N', 'C', w%hc, w%rot, w%buf_nk, w%tmp, w%gemm_ctx)
    2665       558103 :       w%hc(:, :, :) = w%buf_nk(:, :, :)
    2666              :    END SUBROUTINE qs_ot_rotation_gradient_local
    2667              : 
    2668              : ! **************************************************************************************************
    2669              : !> \brief Complete the IRAC pullback, including the vertical polar response for rotation.
    2670              : !> \param w private storage with F in sinp and B in buf3
    2671              : ! **************************************************************************************************
    2672         1027 :    SUBROUTINE qs_ot_ref_gradient_local(w)
    2673              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2674              : 
    2675              :       INTEGER                                            :: i, j
    2676              :       REAL(dp)                                           :: denominator
    2677              : 
    2678         1027 :       CALL qs_ot_complex_multiply_local('N', 'N', w%sx, w%sinp, w%buf_nk, w%tmp, w%gemm_ctx)
    2679         1027 :       CALL qs_ot_complex_multiply_local('N', 'N', w%buf_nk, w%buf3, w%c0, w%tmp, w%gemm_ctx)
    2680      1111177 :       w%c0(:, :, :) = w%hc(:, :, :) - w%c0(:, :, :)
    2681         1027 :       CALL qs_ot_complex_multiply_local('N', 'N', w%c0, w%sinp, w%out, w%tmp, w%gemm_ctx)
    2682         1027 :       IF (.NOT. w%do_rotation) RETURN
    2683              : 
    2684              :       ! Solve P*Z+Z*P=antiherm(B), P=F^(-1), using the shared eigensystem of F.
    2685        99253 :       w%buf1(:, :, 1) = 0.5_dp*(w%buf3(:, :, 1) - TRANSPOSE(w%buf3(:, :, 1)))
    2686        99253 :       w%buf1(:, :, 2) = 0.5_dp*(w%buf3(:, :, 2) + TRANSPOSE(w%buf3(:, :, 2)))
    2687          521 :       CALL qs_ot_complex_multiply_local('N', 'N', w%buf1, w%r, w%buf2, w%buf3(:, :, 1), w%gemm_ctx)
    2688          521 :       CALL qs_ot_complex_multiply_local('C', 'N', w%r, w%buf2, w%buf1, w%buf3(:, :, 1), w%gemm_ctx)
    2689         4747 :       DO j = 1, w%k
    2690        49887 :          DO i = 1, w%k
    2691        45140 :             denominator = 1.0_dp/w%solver%evals(i) + 1.0_dp/w%solver%evals(j)
    2692       139646 :             w%buf1(i, j, :) = w%buf1(i, j, :)/denominator
    2693              :          END DO
    2694              :       END DO
    2695          521 :       CALL qs_ot_complex_multiply_local('N', 'N', w%r, w%buf1, w%buf2, w%buf3(:, :, 1), w%gemm_ctx)
    2696          521 :       CALL qs_ot_complex_multiply_local('N', 'C', w%buf2, w%r, w%buf1, w%buf3(:, :, 1), w%gemm_ctx)
    2697              :       ! buf_nk still contains S*Q. Add the adjoint vertical contribution 2*S*Q*Z.
    2698          521 :       CALL qs_ot_complex_multiply_local('N', 'N', w%buf_nk, w%buf1, w%c0, w%tmp, w%gemm_ctx)
    2699       399771 :       w%out(:, :, :) = w%out(:, :, :) + 2.0_dp*w%c0(:, :, :)
    2700              :    END SUBROUTINE qs_ot_ref_gradient_local
    2701              : 
    2702              : ! **************************************************************************************************
    2703              : !> \brief Contract one STRICT term with its Frechet kernel in the eigenvector basis.
    2704              : !> \param matrix_hc real orbital gradient
    2705              : !> \param matrix_hc_im imaginary orbital gradient
    2706              : !> \param orbital real X or C0
    2707              : !> \param orbital_im imaginary X or C0
    2708              : !> \param kernel real sinc or cos divided differences
    2709              : !> \param RESULT real contraction
    2710              : !> \param result_im imaginary contraction
    2711              : !> \param qs_ot_env channel eigenvectors and scratch
    2712              : ! **************************************************************************************************
    2713         2612 :    SUBROUTINE qs_ot_strict_frechet(matrix_hc, matrix_hc_im, orbital, orbital_im, kernel, &
    2714              :                                    RESULT, result_im, qs_ot_env)
    2715              :       TYPE(dbcsr_type)                                   :: matrix_hc, matrix_hc_im, orbital, &
    2716              :                                                             orbital_im, kernel, RESULT, result_im
    2717              :       TYPE(qs_ot_type)                                   :: qs_ot_env
    2718              : 
    2719              :       CALL qs_ot_complex_multiply('C', 'N', matrix_hc, matrix_hc_im, orbital, orbital_im, &
    2720         2612 :                                   qs_ot_env%matrix_buf2, qs_ot_env%matrix_buf2_im, qs_ot_env%matrix_buf1)
    2721              :       CALL qs_ot_complex_multiply('N', 'N', qs_ot_env%matrix_buf2, qs_ot_env%matrix_buf2_im, &
    2722              :                                   qs_ot_env%matrix_r, qs_ot_env%matrix_r_im, &
    2723         2612 :                                   qs_ot_env%matrix_buf1, qs_ot_env%matrix_buf1_im, qs_ot_env%matrix_buf4)
    2724              :       CALL qs_ot_complex_multiply('C', 'N', qs_ot_env%matrix_r, qs_ot_env%matrix_r_im, &
    2725              :                                   qs_ot_env%matrix_buf1, qs_ot_env%matrix_buf1_im, &
    2726         2612 :                                   qs_ot_env%matrix_buf2, qs_ot_env%matrix_buf2_im, qs_ot_env%matrix_buf4)
    2727         2612 :       CALL dbcsr_hadamard_product(qs_ot_env%matrix_buf2, kernel, RESULT)
    2728         2612 :       CALL dbcsr_hadamard_product(qs_ot_env%matrix_buf2_im, kernel, result_im)
    2729         2612 :    END SUBROUTINE qs_ot_strict_frechet
    2730              : 
    2731              : ! **************************************************************************************************
    2732              : !> \brief Reuse one worker's scratch and pack the local kernel inputs.
    2733              : !> \param w worker storage
    2734              : !> \param qs_ot_env channel with an already prepared tangent metric
    2735              : !> \param matrix_hc real orbital gradient
    2736              : !> \param matrix_hc_im imaginary orbital gradient
    2737              : ! **************************************************************************************************
    2738         4405 :    SUBROUTINE qs_ot_load_local_work(w, qs_ot_env, matrix_hc, matrix_hc_im)
    2739              :       TYPE(qs_ot_local_work_type), INTENT(INOUT)         :: w
    2740              :       TYPE(qs_ot_type), INTENT(IN)                       :: qs_ot_env
    2741              :       TYPE(dbcsr_type), INTENT(IN)                       :: matrix_hc, matrix_hc_im
    2742              : 
    2743              :       INTEGER                                            :: k, n, o
    2744              : 
    2745         2671 :       CALL dbcsr_get_info(qs_ot_env%matrix_x, nfullrows_total=n, nfullcols_total=k)
    2746         2671 :       CALL qs_ot_allocate_local_work(w, n, k)
    2747         2671 :       IF (ALLOCATED(w%hc)) THEN
    2748         1415 :          IF (SIZE(w%hc, 1) /= n .OR. SIZE(w%hc, 2) /= k) DEALLOCATE (w%hc)
    2749              :       END IF
    2750         7911 :       IF (.NOT. ALLOCATED(w%hc)) ALLOCATE (w%hc(n, k, 2))
    2751         2671 :       CALL qs_ot_load_local_pair(matrix_hc, matrix_hc_im, w%hc)
    2752         2671 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_x, qs_ot_env%matrix_x_im, w%x)
    2753         2671 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_sx, qs_ot_env%matrix_sx_im, w%sx)
    2754         2671 :       w%do_rotation = qs_ot_env%settings%do_rotation
    2755         2671 :       IF (w%do_rotation) THEN
    2756          521 :          CALL qs_ot_prepare_local_spectrum(w)
    2757          521 :          CALL qs_ot_load_local_pair(qs_ot_env%rot_mat_x, qs_ot_env%rot_mat_x_im, w%rot)
    2758              :       END IF
    2759         2671 :       IF (qs_ot_env%settings%ot_algorithm == 'REF') THEN
    2760          937 :          CALL qs_ot_load_local_pair(qs_ot_env%matrix_ref_inv_sqrt, qs_ot_env%matrix_ref_inv_sqrt_im, w%sinp)
    2761          937 :          RETURN
    2762              :       END IF
    2763         1734 :       IF (w%do_rotation) THEN
    2764           90 :          IF (ALLOCATED(w%cosp)) THEN
    2765            0 :             IF (SIZE(w%cosp, 1) /= k) DEALLOCATE (w%cosp)
    2766              :          END IF
    2767          450 :          IF (.NOT. ALLOCATED(w%cosp)) ALLOCATE (w%cosp(k, k, 2))
    2768           90 :          CALL qs_ot_load_local_pair(qs_ot_env%matrix_cosp, qs_ot_env%matrix_cosp_im, w%cosp)
    2769              :       END IF
    2770         1734 :       CALL dbcsr_get_info(qs_ot_env%matrix_sc0, nfullcols_total=o)
    2771         1734 :       IF (ALLOCATED(w%sc0)) THEN
    2772         1050 :          IF (SIZE(w%sc0, 1) /= n .OR. SIZE(w%buf1_ortho, 2) /= k .OR. o /= SIZE(w%sc0, 2)) THEN
    2773            0 :             DEALLOCATE (w%sc0, w%target, w%os, w%buf1_ortho, w%buf2_ortho, w%tmp_ortho)
    2774              :          END IF
    2775              :       END IF
    2776         1734 :       IF (.NOT. ALLOCATED(w%sc0)) THEN
    2777            0 :          ALLOCATE (w%sc0(n, o, 2), w%target(n, o, 2), w%os(o, o, 2), &
    2778        13680 :                    w%buf1_ortho(o, k, 2), w%buf2_ortho(o, k, 2), w%tmp_ortho(o, k))
    2779              :       END IF
    2780         1734 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_c0, qs_ot_env%matrix_c0_im, w%c0)
    2781         1734 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_r, qs_ot_env%matrix_r_im, w%r)
    2782         1734 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_sinp, qs_ot_env%matrix_sinp_im, w%sinp)
    2783         1734 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_sc0, qs_ot_env%matrix_sc0_im, w%sc0)
    2784         1734 :       CALL qs_ot_load_local_pair(qs_ot_env%matrix_os, qs_ot_env%matrix_os_im, w%os)
    2785         1734 :       CALL copy_dbcsr_to_local(qs_ot_env%matrix_sinp_b, w%sinp_b)
    2786         1734 :       CALL copy_dbcsr_to_local(qs_ot_env%matrix_cosp_b, w%cosp_b)
    2787         1734 :       IF (ASSOCIATED(qs_ot_env%preconditioner)) THEN
    2788         1734 :          CALL qs_ot_load_local_pair(qs_ot_env%matrix_psc0, qs_ot_env%matrix_psc0_im, w%target)
    2789              :       ELSE
    2790            0 :          w%target(:, :, :) = w%sc0(:, :, :)
    2791              :       END IF
    2792              :    END SUBROUTINE qs_ot_load_local_work
    2793              : 
    2794              : ! **************************************************************************************************
    2795              : !> \brief Pack a complex pair, allowing different sparse patterns in its two components.
    2796              : !> \param matrix_re real DBCSR matrix
    2797              : !> \param matrix_im imaginary DBCSR matrix
    2798              : !> \param dense dense real/imaginary pair
    2799              : ! **************************************************************************************************
    2800        26773 :    SUBROUTINE qs_ot_load_local_pair(matrix_re, matrix_im, dense)
    2801              :       TYPE(dbcsr_type), INTENT(IN)                       :: matrix_re, matrix_im
    2802              :       REAL(dp), INTENT(OUT)                              :: dense(:, :, :)
    2803              : 
    2804        26773 :       CALL copy_dbcsr_to_local(matrix_re, dense(:, :, 1))
    2805        26773 :       CALL copy_dbcsr_to_local(matrix_im, dense(:, :, 2))
    2806        26773 :    END SUBROUTINE qs_ot_load_local_pair
    2807              : 
    2808              : ! **************************************************************************************************
    2809              : !> \brief Multiply dense real/imaginary pairs in the same four-product order as DBCSR.
    2810              : !> \param op_a N or C
    2811              : !> \param op_b N or C
    2812              : !> \param a left operand
    2813              : !> \param b right operand
    2814              : !> \param c result
    2815              : !> \param tmp real workspace, at least as large as one component of C
    2816              : !> \param gemm_ctx caller-owned GEMM context
    2817              : ! **************************************************************************************************
    2818        47149 :    SUBROUTINE qs_ot_complex_multiply_pair_local(op_a, op_b, a, b, c, tmp, gemm_ctx)
    2819              :       CHARACTER(LEN=1), INTENT(IN)                       :: op_a, op_b
    2820              :       REAL(dp), CONTIGUOUS, INTENT(IN)                   :: a(:, :, :), b(:, :, :)
    2821              :       REAL(dp), CONTIGUOUS, INTENT(INOUT)                :: c(:, :, :), tmp(:, :)
    2822              :       TYPE(local_gemm_ctxt_type)                         :: gemm_ctx
    2823              : 
    2824              :       CALL qs_ot_complex_multiply_parts_local(op_a, op_b, a(:, :, 1), a(:, :, 2), &
    2825        47149 :                                               b(:, :, 1), b(:, :, 2), c(:, :, 1), c(:, :, 2), tmp, gemm_ctx)
    2826        47149 :    END SUBROUTINE qs_ot_complex_multiply_pair_local
    2827              : 
    2828              : ! **************************************************************************************************
    2829              : !> \brief Multiply separate real/imaginary arrays, including cached overlap matrices.
    2830              : !> \param op_a N or C
    2831              : !> \param op_b N or C
    2832              : !> \param a_re real left operand
    2833              : !> \param a_im imaginary left operand
    2834              : !> \param b_re real right operand
    2835              : !> \param b_im imaginary right operand
    2836              : !> \param c_re real result
    2837              : !> \param c_im imaginary result
    2838              : !> \param tmp real workspace
    2839              : !> \param gemm_ctx caller-owned GEMM context
    2840              : ! **************************************************************************************************
    2841        51491 :    SUBROUTINE qs_ot_complex_multiply_parts_local(op_a, op_b, a_re, a_im, b_re, b_im, &
    2842        51491 :                                                  c_re, c_im, tmp, gemm_ctx)
    2843              :       CHARACTER(LEN=1), INTENT(IN)                       :: op_a, op_b
    2844              :       REAL(dp), CONTIGUOUS, INTENT(IN)                   :: a_re(:, :), a_im(:, :), b_re(:, :), &
    2845              :                                                             b_im(:, :)
    2846              :       REAL(dp), CONTIGUOUS, INTENT(INOUT)                :: c_re(:, :), c_im(:, :), tmp(:, :)
    2847              :       TYPE(local_gemm_ctxt_type)                         :: gemm_ctx
    2848              : 
    2849              :       CHARACTER(LEN=1)                                   :: trans_a, trans_b
    2850              :       INTEGER                                            :: k, m, n
    2851              :       REAL(dp)                                           :: sign_a, sign_b
    2852              : 
    2853        51491 :       CPASSERT(op_a == 'N' .OR. op_a == 'C')
    2854        51491 :       CPASSERT(op_b == 'N' .OR. op_b == 'C')
    2855        51491 :       trans_a = MERGE('N', 'T', op_a == 'N')
    2856        51491 :       trans_b = MERGE('N', 'T', op_b == 'N')
    2857        51491 :       sign_a = MERGE(1.0_dp, -1.0_dp, op_a == 'N')
    2858        51491 :       sign_b = MERGE(1.0_dp, -1.0_dp, op_b == 'N')
    2859        51491 :       m = SIZE(c_re, 1)
    2860        51491 :       n = SIZE(c_re, 2)
    2861        51491 :       k = MERGE(SIZE(a_re, 2), SIZE(a_re, 1), op_a == 'N')
    2862              :       CALL gemm_ctx%gemm(trans_a, trans_b, m, n, k, 1.0_dp, a_re, SIZE(a_re, 1), &
    2863        51491 :                          b_re, SIZE(b_re, 1), 0.0_dp, c_re, SIZE(c_re, 1))
    2864              :       CALL gemm_ctx%gemm(trans_a, trans_b, m, n, k, 1.0_dp, a_im, SIZE(a_im, 1), &
    2865        51491 :                          b_im, SIZE(b_im, 1), 0.0_dp, tmp, SIZE(tmp, 1))
    2866      9040231 :       c_re(:, :) = c_re(:, :) - sign_a*sign_b*tmp(1:m, 1:n)
    2867              :       CALL gemm_ctx%gemm(trans_a, trans_b, m, n, k, sign_b, a_re, SIZE(a_re, 1), &
    2868        51491 :                          b_im, SIZE(b_im, 1), 0.0_dp, c_im, SIZE(c_im, 1))
    2869              :       CALL gemm_ctx%gemm(trans_a, trans_b, m, n, k, sign_a, a_im, SIZE(a_im, 1), &
    2870        51491 :                          b_re, SIZE(b_re, 1), 0.0_dp, tmp, SIZE(tmp, 1))
    2871      9040231 :       c_im(:, :) = c_im(:, :) + tmp(1:m, 1:n)
    2872        51491 :    END SUBROUTINE qs_ot_complex_multiply_parts_local
    2873              : 
    2874              : ! **************************************************************************************************
    2875              : !> \brief Contract one STRICT term in the local eigenvector basis.
    2876              : !> \param w channel matrices and scratch
    2877              : !> \param orbital dense X or C0 pair
    2878              : !> \param kernel real sinc or cos divided differences
    2879              : !> \param RESULT contraction in the eigenvector basis
    2880              : ! **************************************************************************************************
    2881         3520 :    SUBROUTINE qs_ot_strict_frechet_local(w, orbital, kernel, RESULT)
    2882              :       TYPE(qs_ot_local_work_type)                        :: w
    2883              :       REAL(dp), CONTIGUOUS, INTENT(IN)                   :: orbital(:, :, :), kernel(:, :)
    2884              :       REAL(dp), CONTIGUOUS, INTENT(INOUT)                :: result(:, :, :)
    2885              : 
    2886         3520 :       CALL qs_ot_complex_multiply_local('C', 'N', w%hc, orbital, w%buf2, w%buf1(:, :, 1), w%gemm_ctx)
    2887         3520 :       CALL qs_ot_complex_multiply_local('N', 'N', w%buf2, w%r, w%buf1, w%buf4(:, :, 1), w%gemm_ctx)
    2888         3520 :       CALL qs_ot_complex_multiply_local('C', 'N', w%r, w%buf1, w%buf2, w%buf4(:, :, 1), w%gemm_ctx)
    2889       138416 :       RESULT(:, :, 1) = w%buf2(:, :, 1)*kernel(:, :)
    2890       138416 :       RESULT(:, :, 2) = w%buf2(:, :, 2)*kernel(:, :)
    2891         3520 :    END SUBROUTINE qs_ot_strict_frechet_local
    2892              : 
    2893              : ! **************************************************************************************************
    2894              : !> \brief Evaluate and project one local STRICT gradient without allocating or calling DBCSR.
    2895              : !> \param w channel matrices and scratch, with a prepared inverse tangent metric
    2896              : ! **************************************************************************************************
    2897         1760 :    SUBROUTINE qs_ot_strict_gradient_local(w)
    2898              :       TYPE(qs_ot_local_work_type)                        :: w
    2899              : 
    2900              :       INTEGER                                            :: i, j
    2901              :       REAL(dp)                                           :: im, re
    2902              : 
    2903              :       ! Direct term, followed by the X*sinc and C0*cos Frechet contributions.
    2904         1760 :       CALL qs_ot_complex_multiply_local('N', 'N', w%hc, w%sinp, w%out, w%tmp, w%gemm_ctx)
    2905         1760 :       CALL qs_ot_strict_frechet_local(w, w%x, w%sinp_b, w%buf3)
    2906         1760 :       CALL qs_ot_strict_frechet_local(w, w%c0, w%cosp_b, w%buf4)
    2907       140176 :       w%buf3(:, :, :) = w%buf3(:, :, :) + w%buf4(:, :, :)
    2908         1760 :       CALL qs_ot_complex_multiply_local('N', 'N', w%r, w%buf3, w%buf1, w%buf2(:, :, 1), w%gemm_ctx)
    2909         1760 :       CALL qs_ot_complex_multiply_local('N', 'C', w%buf1, w%r, w%buf3, w%buf2(:, :, 1), w%gemm_ctx)
    2910              : 
    2911              :       ! Merge each conjugate pair in place, without a transpose temporary.
    2912        10056 :       DO j = 1, w%k
    2913         8296 :          w%buf3(j, j, 1) = w%buf3(j, j, 1) + w%buf3(j, j, 1)
    2914         8296 :          w%buf3(j, j, 2) = 0.0_dp
    2915        35484 :          DO i = j + 1, w%k
    2916        25428 :             re = w%buf3(i, j, 1) + w%buf3(j, i, 1)
    2917        25428 :             im = w%buf3(i, j, 2) - w%buf3(j, i, 2)
    2918        25428 :             w%buf3(i, j, 1) = re
    2919        25428 :             w%buf3(j, i, 1) = re
    2920        25428 :             w%buf3(i, j, 2) = im
    2921        33724 :             w%buf3(j, i, 2) = -im
    2922              :          END DO
    2923              :       END DO
    2924         1760 :       CALL qs_ot_complex_multiply_local('N', 'N', w%sx, w%buf3, w%buf_nk, w%tmp, w%gemm_ctx)
    2925       478912 :       w%out(:, :, :) = w%out(:, :, :) + w%buf_nk(:, :, :)
    2926              : 
    2927              :       ! Tangent projection with the same preconditioned reference and inverse metric.
    2928         1760 :       CALL qs_ot_complex_multiply_local('C', 'N', w%target, w%out, w%buf1_ortho, w%tmp_ortho, w%gemm_ctx)
    2929         1760 :       CALL qs_ot_complex_multiply_local('N', 'N', w%os, w%buf1_ortho, w%buf2_ortho, w%tmp_ortho, w%gemm_ctx)
    2930         1760 :       CALL qs_ot_complex_multiply_local('N', 'N', w%sc0, w%buf2_ortho, w%buf_nk, w%tmp, w%gemm_ctx)
    2931       478912 :       w%out(:, :, :) = w%out(:, :, :) - w%buf_nk(:, :, :)
    2932         1760 :    END SUBROUTINE qs_ot_strict_gradient_local
    2933              : 
    2934         1488 : END MODULE qs_ot_kp
        

Generated by: LCOV version 2.0-1