LCOV - code coverage report
Current view: top level - src - qs_ot_complex_ref_unittest.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:9ccafda) Lines: 98.4 % 1820 1791
Test Date: 2026-10-15 00:55:44 Functions: 100.0 % 44 44

            Line data    Source code
       1              : !--------------------------------------------------------------------------------------------------!
       2              : !   CP2K: A general program to perform molecular dynamics simulations                              !
       3              : !   Copyright 2000-2026 CP2K developers group <https://cp2k.org>                                   !
       4              : !                                                                                                  !
       5              : !   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
       6              : !--------------------------------------------------------------------------------------------------!
       7              : 
       8              : ! **************************************************************************************************
       9              : !> \brief Regressions for complex k-point OT derivatives and preconditioner guards.
      10              : ! **************************************************************************************************
      11          320 : PROGRAM qs_ot_complex_ref_unittest
      12            2 :    USE bibliography,                    ONLY: add_all_references
      13              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_create,&
      14              :                                               cp_blacs_env_release,&
      15              :                                               cp_blacs_env_type
      16              :    USE cp_cfm_types,                    ONLY: cp_cfm_create,&
      17              :                                               cp_cfm_get_element,&
      18              :                                               cp_cfm_get_submatrix,&
      19              :                                               cp_cfm_release,&
      20              :                                               cp_cfm_set_all,&
      21              :                                               cp_cfm_set_element,&
      22              :                                               cp_cfm_set_submatrix,&
      23              :                                               cp_cfm_type
      24              :    USE cp_dbcsr_api,                    ONLY: &
      25              :         dbcsr_create, dbcsr_distribution_new, dbcsr_distribution_release, dbcsr_distribution_type, &
      26              :         dbcsr_finalize, dbcsr_finalize_lib, dbcsr_get_block_p, dbcsr_init_lib, dbcsr_put_block, &
      27              :         dbcsr_release, dbcsr_reserve_blocks, dbcsr_type, dbcsr_type_no_symmetry
      28              :    USE cp_fm_basic_linalg,              ONLY: cp_complex_fm_gemm
      29              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      30              :                                               cp_fm_struct_release,&
      31              :                                               cp_fm_struct_type
      32              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      33              :                                               cp_fm_get_submatrix,&
      34              :                                               cp_fm_release,&
      35              :                                               cp_fm_set_submatrix,&
      36              :                                               cp_fm_type
      37              :    USE cp_log_handling,                 ONLY: cp_add_default_logger,&
      38              :                                               cp_logger_create,&
      39              :                                               cp_logger_release,&
      40              :                                               cp_logger_type,&
      41              :                                               cp_rm_default_logger
      42              :    USE input_constants,                 ONLY: &
      43              :         ot_precond_fermi_low_rank, ot_precond_full_all, ot_precond_full_all_covariant, &
      44              :         ot_precond_full_kinetic, ot_precond_full_single, ot_precond_full_single_inverse, &
      45              :         ot_precond_none, ot_precond_s_inverse, ot_precond_solver_chebyshev, &
      46              :         ot_precond_solver_default, ot_precond_solver_direct, ot_precond_solver_inv_chol
      47              :    USE kinds,                           ONLY: dp
      48              :    USE kpoint_methods,                  ONLY: kpoint_ot_energy_weighted_density
      49              :    USE machine,                         ONLY: default_output_unit
      50              :    USE mathlib,                         ONLY: diag_complex
      51              :    USE message_passing,                 ONLY: mp_comm_self,&
      52              :                                               mp_comm_type,&
      53              :                                               mp_para_env_release,&
      54              :                                               mp_para_env_type,&
      55              :                                               mp_world_finalize,&
      56              :                                               mp_world_init
      57              :    USE preconditioner,                  ONLY: apply_preconditioner
      58              :    USE preconditioner_makes,            ONLY: make_complex_fermi_low_rank,&
      59              :                                               make_complex_full_all,&
      60              :                                               make_complex_full_all_covariant,&
      61              :                                               make_complex_full_kinetic,&
      62              :                                               make_complex_full_s_inverse,&
      63              :                                               make_complex_full_single,&
      64              :                                               make_complex_full_single_inverse
      65              :    USE preconditioner_types,            ONLY: destroy_preconditioner,&
      66              :                                               init_preconditioner,&
      67              :                                               preconditioner_type
      68              :    USE qs_ot,                           ONLY: &
      69              :         qs_ot_antihermitian_spectral_norm, qs_ot_apply_complex_frechet_dbcsr, &
      70              :         qs_ot_complex_exp_frechet_kernel, qs_ot_density_secant_hessian, &
      71              :         qs_ot_density_secant_orbital_overlaps, qs_ot_density_secant_projected_hessian, &
      72              :         qs_ot_density_tangent, qs_ot_finite_rotation_response, qs_ot_fixed_n_energy_gradient, &
      73              :         qs_ot_fixed_n_energy_hessian, qs_ot_fixed_n_multigroup_schur_block, &
      74              :         qs_ot_fixed_n_projector_frechet, qs_ot_fixed_n_schur_block, qs_ot_generate_rotation, &
      75              :         qs_ot_get_derivative_complex, qs_ot_get_derivative_ref_complex, &
      76              :         qs_ot_get_orbitals_ref_complex, qs_ot_get_p_complex, qs_ot_projected_response_update, &
      77              :         qs_ot_rot_mat_derivative, qs_ot_symmetric_abs_solve, qs_ot_symmetric_sr1_update
      78              :    USE qs_ot_types,                     ONLY: qs_ot_kpoint_preconditioner_scale,&
      79              :                                               qs_ot_kpoint_preconditioner_solver_supported,&
      80              :                                               qs_ot_kpoint_preconditioner_supported,&
      81              :                                               qs_ot_type
      82              :    USE reference_manager,               ONLY: remove_all_references
      83              : 
      84              :    IMPLICIT NONE
      85              : 
      86              :    INTEGER, PARAMETER                                 :: k = 2, n = 3
      87              : 
      88              :    COMPLEX(KIND=dp), DIMENSION(n, k)                  :: direction, gradient, gradient_old, hc, q, x
      89              :    COMPLEX(KIND=dp), DIMENSION(n, n)                  :: hamiltonian
      90              :    COMPLEX(KIND=dp), DIMENSION(k, k)                  :: b, inverse_sqrt
      91              :    INTEGER                                            :: io_unit, mynode, nfail
      92            4 :    INTEGER, DIMENSION(:), POINTER                     :: col_dist, col_k, row_dist, row_k, row_n
      93            2 :    INTEGER, DIMENSION(:, :), POINTER                  :: pgrid
      94              :    LOGICAL                                            :: found
      95              :    REAL(KIND=dp)                                      :: error, fd_slope, hstep, old_error, slope
      96            2 :    REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block_p
      97              :    TYPE(cp_logger_type), POINTER                      :: logger
      98              :    TYPE(dbcsr_distribution_type)                     :: dist_kk, dist_nk
      99              :    TYPE(dbcsr_type), TARGET                           :: b_im, b_re, f_im, f_re, g_im, g_re, &
     100              :                                                          hc_im, hc_re, q_im, q_re, sx_im, sx_re, &
     101              :                                                          tmp_kk, x_im, x_re
     102              :    TYPE(dbcsr_type), POINTER                          :: hc_im_p, hc_re_p
     103              :    TYPE(mp_comm_type)                                 :: mp_comm
     104              :    TYPE(mp_para_env_type), POINTER                    :: para_env
     105          324 :    TYPE(qs_ot_type)                                   :: ot_env
     106              : 
     107            2 :    nfail = 0
     108            2 :    CALL test_preconditioner_support(nfail)
     109            2 :    NULLIFY (hc_im_p, hc_re_p, logger, para_env)
     110            2 :    CALL mp_world_init(mp_comm)
     111            2 :    mynode = mp_comm%mepos
     112            2 :    io_unit = -1
     113            2 :    IF (mynode == 0) io_unit = default_output_unit
     114            2 :    CALL test_antihermitian_spectral_norm(io_unit, nfail)
     115            2 :    CALL test_fixed_n_mermin_energy(mynode, nfail)
     116            2 :    CALL test_fixed_n_projector_frechet(mynode, nfail)
     117            2 :    CALL test_finite_rotation_response(mynode, nfail)
     118            2 :    CALL test_fixed_n_rotation_schur(mynode, nfail)
     119            2 :    CALL test_fixed_n_multigroup_schur(mynode, nfail)
     120            2 :    CALL test_roks_fixed_n_rotation_schur(mynode, nfail)
     121            2 :    CALL test_symmetric_abs_solve(mynode, nfail)
     122            2 :    CALL test_projected_response_update(mynode, nfail)
     123            2 :    CALL test_symmetric_sr1_update(mynode, nfail)
     124            2 :    CALL test_density_secant_hessian(mynode, nfail)
     125            2 :    CALL test_joint_spin_density_secant(mynode, nfail)
     126            2 :    CALL test_density_tangent(mynode, nfail)
     127            2 :    CALL test_density_secant_moving_subspace(mynode, nfail)
     128            2 :    ALLOCATE (para_env)
     129            2 :    CALL para_env%from_dup(mp_comm)
     130              :    CALL cp_logger_create(logger, para_env=para_env, default_global_unit_nr=io_unit, &
     131            2 :                          close_global_unit_on_dealloc=.FALSE.)
     132            2 :    CALL cp_add_default_logger(logger)
     133            2 :    CALL add_all_references()
     134            2 :    CALL dbcsr_init_lib(mp_comm%get_handle(), io_unit)
     135            2 :    CALL test_real_rotation_frechet(para_env, nfail)
     136            2 :    CALL test_complex_rotation_frechet(para_env, nfail)
     137            2 :    CALL test_sparse_frechet_patterns(para_env, nfail)
     138            2 :    CALL test_complex_ref_rotation(para_env, nfail)
     139            2 :    CALL test_split_complex_fm_gemm(para_env, io_unit, nfail)
     140            2 :    CALL test_complex_preconditioner_gauge(para_env, nfail)
     141            2 :    CALL test_complex_occupation_block_preconditioner(para_env, nfail)
     142            2 :    CALL test_kpoint_ot_energy_weighted_density(para_env, io_unit, nfail)
     143              : 
     144              :    x(:, 1) = [CMPLX(1.10_dp, 0.10_dp, KIND=dp), CMPLX(0.20_dp, -0.30_dp, KIND=dp), &
     145            8 :               CMPLX(-0.10_dp, 0.20_dp, KIND=dp)]
     146              :    x(:, 2) = [CMPLX(0.30_dp, 0.40_dp, KIND=dp), CMPLX(0.90_dp, -0.20_dp, KIND=dp), &
     147            8 :               CMPLX(0.25_dp, 0.10_dp, KIND=dp)]
     148              :    direction(:, 1) = [CMPLX(0.17_dp, -0.09_dp, KIND=dp), CMPLX(-0.21_dp, 0.13_dp, KIND=dp), &
     149            8 :                       CMPLX(0.08_dp, 0.19_dp, KIND=dp)]
     150              :    direction(:, 2) = [CMPLX(-0.12_dp, 0.23_dp, KIND=dp), CMPLX(0.16_dp, 0.07_dp, KIND=dp), &
     151            8 :                       CMPLX(-0.14_dp, -0.11_dp, KIND=dp)]
     152              : 
     153              :    hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     154            2 :    hamiltonian(1, 1) = CMPLX(0.70_dp, 0.0_dp, KIND=dp)
     155            2 :    hamiltonian(2, 2) = CMPLX(-0.40_dp, 0.0_dp, KIND=dp)
     156            2 :    hamiltonian(3, 3) = CMPLX(1.10_dp, 0.0_dp, KIND=dp)
     157            2 :    hamiltonian(1, 2) = CMPLX(0.20_dp, 0.10_dp, KIND=dp)
     158            2 :    hamiltonian(2, 1) = CONJG(hamiltonian(1, 2))
     159            2 :    hamiltonian(1, 3) = CMPLX(-0.10_dp, 0.05_dp, KIND=dp)
     160            2 :    hamiltonian(3, 1) = CONJG(hamiltonian(1, 3))
     161            2 :    hamiltonian(2, 3) = CMPLX(0.15_dp, 0.20_dp, KIND=dp)
     162            2 :    hamiltonian(3, 2) = CONJG(hamiltonian(2, 3))
     163              : 
     164           38 :    CALL dense_inverse_sqrt(MATMUL(CONJG(TRANSPOSE(x)), x), inverse_sqrt)
     165           38 :    q = MATMUL(x, inverse_sqrt)
     166           70 :    hc = 2.0_dp*MATMUL(hamiltonian, q)
     167              : 
     168            2 :    ALLOCATE (pgrid(0:0, 0:0), row_dist(1), col_dist(1), row_n(1), row_k(1), col_k(1))
     169            6 :    pgrid(:, :) = 0
     170            4 :    row_dist(:) = 0
     171            4 :    col_dist(:) = 0
     172            4 :    row_n(:) = n
     173            4 :    row_k(:) = k
     174            4 :    col_k(:) = k
     175              :    CALL dbcsr_distribution_new(dist_nk, group=mp_comm_self%get_handle(), pgrid=pgrid, &
     176            2 :                                row_dist=row_dist, col_dist=col_dist)
     177              :    CALL dbcsr_distribution_new(dist_kk, group=mp_comm_self%get_handle(), pgrid=pgrid, &
     178            2 :                                row_dist=row_dist, col_dist=col_dist)
     179            2 :    CALL test_complex_strict_derivative(dist_nk, dist_kk, row_n, row_k, col_k, para_env, nfail)
     180              : 
     181            2 :    CALL create_one_block(x_re, "complex_ref_x_re", dist_nk, row_n, col_k)
     182            2 :    CALL create_one_block(x_im, "complex_ref_x_im", dist_nk, row_n, col_k)
     183            2 :    CALL create_one_block(sx_re, "complex_ref_sx_re", dist_nk, row_n, col_k)
     184            2 :    CALL create_one_block(sx_im, "complex_ref_sx_im", dist_nk, row_n, col_k)
     185            2 :    CALL create_one_block(hc_re, "complex_ref_hc_re", dist_nk, row_n, col_k)
     186            2 :    CALL create_one_block(hc_im, "complex_ref_hc_im", dist_nk, row_n, col_k)
     187            2 :    CALL create_one_block(g_re, "complex_ref_g_re", dist_nk, row_n, col_k)
     188            2 :    CALL create_one_block(g_im, "complex_ref_g_im", dist_nk, row_n, col_k)
     189            2 :    CALL create_one_block(q_re, "complex_ref_q_re", dist_nk, row_n, col_k)
     190            2 :    CALL create_one_block(q_im, "complex_ref_q_im", dist_nk, row_n, col_k)
     191            2 :    CALL create_one_block(f_re, "complex_ref_f_re", dist_kk, row_k, col_k)
     192            2 :    CALL create_one_block(f_im, "complex_ref_f_im", dist_kk, row_k, col_k)
     193            2 :    CALL create_one_block(b_re, "complex_ref_b_re", dist_kk, row_k, col_k)
     194            2 :    CALL create_one_block(b_im, "complex_ref_b_im", dist_kk, row_k, col_k)
     195            2 :    CALL create_one_block(tmp_kk, "complex_ref_tmp_kk", dist_kk, row_k, col_k)
     196              : 
     197            2 :    CALL put_complex_pair(x_re, x_im, x)
     198            2 :    CALL put_complex_pair(sx_re, sx_im, x)
     199            2 :    CALL put_complex_pair(hc_re, hc_im, hc)
     200            2 :    CALL put_complex_pair(f_re, f_im, inverse_sqrt)
     201              : 
     202            2 :    ot_env%has_complex_kpoint_state = .TRUE.
     203            2 :    ot_env%settings%eps_irac_filter_matrix = 0.0_dp
     204            2 :    ot_env%matrix_x => x_re
     205            2 :    ot_env%matrix_x_im => x_im
     206            2 :    ot_env%matrix_sx => sx_re
     207            2 :    ot_env%matrix_sx_im => sx_im
     208            2 :    ot_env%matrix_gx => g_re
     209            2 :    ot_env%matrix_gx_im => g_im
     210            2 :    ot_env%matrix_ref_inv_sqrt => f_re
     211            2 :    ot_env%matrix_ref_inv_sqrt_im => f_im
     212            2 :    ot_env%buf1_k_k_sym => b_re
     213            2 :    ot_env%buf2_k_k_sym => b_im
     214            2 :    ot_env%buf3_k_k_sym => tmp_kk
     215            2 :    ot_env%buf1_n_k => q_re
     216            2 :    ot_env%buf1_n_k_dp => q_im
     217              : 
     218            2 :    hc_re_p => hc_re
     219            2 :    hc_im_p => hc_im
     220            2 :    CALL qs_ot_get_derivative_ref_complex(hc_re_p, hc_im_p, ot_env)
     221              : 
     222            2 :    gradient(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     223            2 :    CALL dbcsr_get_block_p(g_re, 1, 1, block_p, found)
     224           18 :    IF (found) gradient = gradient + CMPLX(block_p, 0.0_dp, KIND=dp)
     225            2 :    CALL dbcsr_get_block_p(g_im, 1, 1, block_p, found)
     226           18 :    IF (found) gradient = gradient + CMPLX(0.0_dp, block_p, KIND=dp)
     227              : 
     228            2 :    hstep = 1.0E-6_dp
     229              :    fd_slope = (polar_energy(x + hstep*direction, hamiltonian) - &
     230           34 :                polar_energy(x - hstep*direction, hamiltonian))/(2.0_dp*hstep)
     231           18 :    slope = REAL(SUM(CONJG(gradient)*direction), KIND=dp)
     232            2 :    error = ABS(slope - fd_slope)
     233              : 
     234           38 :    b = MATMUL(CONJG(TRANSPOSE(x)), hc)
     235           54 :    gradient_old = hc - MATMUL(x, b)
     236           18 :    old_error = ABS(REAL(SUM(CONJG(gradient_old)*direction), KIND=dp) - fd_slope)
     237            2 :    IF (error > 5.0E-8_dp .OR. old_error < 1.0E-3_dp) nfail = nfail + 1
     238              : 
     239            2 :    IF (mynode == 0) THEN
     240            1 :       WRITE (io_unit, '(A,3(1X,ES13.6))') "complex finite-REF pullback: fd/predicted/error", &
     241            2 :          fd_slope, slope, error
     242            1 :       WRITE (io_unit, '(A,1X,ES13.6)') "discarded projected-gradient error", old_error
     243              :    END IF
     244              : 
     245            2 :    CALL dbcsr_release(b_im)
     246            2 :    CALL dbcsr_release(b_re)
     247            2 :    CALL dbcsr_release(tmp_kk)
     248            2 :    CALL dbcsr_release(f_im)
     249            2 :    CALL dbcsr_release(f_re)
     250            2 :    CALL dbcsr_release(q_im)
     251            2 :    CALL dbcsr_release(q_re)
     252            2 :    CALL dbcsr_release(g_im)
     253            2 :    CALL dbcsr_release(g_re)
     254            2 :    CALL dbcsr_release(hc_im)
     255            2 :    CALL dbcsr_release(hc_re)
     256            2 :    CALL dbcsr_release(sx_im)
     257            2 :    CALL dbcsr_release(sx_re)
     258            2 :    CALL dbcsr_release(x_im)
     259            2 :    CALL dbcsr_release(x_re)
     260            2 :    CALL dbcsr_distribution_release(dist_kk)
     261            2 :    CALL dbcsr_distribution_release(dist_nk)
     262            2 :    DEALLOCATE (pgrid, row_dist, col_dist, row_n, row_k, col_k)
     263              : 
     264            2 :    CALL dbcsr_finalize_lib()
     265            2 :    CALL remove_all_references()
     266            2 :    CALL cp_rm_default_logger()
     267            2 :    CALL cp_logger_release(logger)
     268            2 :    CALL mp_para_env_release(para_env)
     269            2 :    CALL mp_world_finalize()
     270              : 
     271            6 :    IF (nfail > 0) ERROR STOP "qs_ot_complex_ref_unittest failed"
     272              : 
     273              : CONTAINS
     274              : 
     275              : ! **************************************************************************************************
     276              : !> \brief Check split-complex full-matrix multiplication including adjoints.
     277              : !> \param para_env parallel environment
     278              : !> \param io_unit output unit
     279              : !> \param nfail accumulated number of failures
     280              : ! **************************************************************************************************
     281            2 :    SUBROUTINE test_split_complex_fm_gemm(para_env, io_unit, nfail)
     282              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     283              :       INTEGER, INTENT(IN)                                :: io_unit
     284              :       INTEGER, INTENT(INOUT)                             :: nfail
     285              : 
     286              :       INTEGER, PARAMETER                                 :: ndim = 3
     287              : 
     288              :       COMPLEX(KIND=dp), DIMENSION(ndim, ndim)            :: a, actual, b, expected
     289              :       INTEGER                                            :: i, j
     290              :       REAL(KIND=dp)                                      :: error_cn, error_nc, error_nn
     291              :       REAL(KIND=dp), DIMENSION(ndim, ndim)               :: actual_im, actual_re
     292              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
     293              :       TYPE(cp_fm_struct_type), POINTER                   :: matrix_struct
     294              :       TYPE(cp_fm_type)                                   :: a_im, a_re, b_im, b_re, c_im, c_re
     295              : 
     296            2 :       NULLIFY (blacs_env, matrix_struct)
     297            8 :       DO j = 1, ndim
     298           26 :          DO i = 1, ndim
     299           18 :             a(i, j) = CMPLX(0.13_dp*i - 0.07_dp*j, 0.05_dp*i*j - 0.11_dp*j, KIND=dp)
     300           24 :             b(i, j) = CMPLX(-0.09_dp*i + 0.17_dp*j, 0.08_dp*i*j + 0.04_dp*i, KIND=dp)
     301              :          END DO
     302              :       END DO
     303              : 
     304            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=para_env)
     305              :       CALL cp_fm_struct_create(matrix_struct, nrow_global=ndim, ncol_global=ndim, &
     306            2 :                                context=blacs_env, para_env=para_env)
     307            2 :       CALL cp_fm_create(a_re, matrix_struct)
     308            2 :       CALL cp_fm_create(a_im, matrix_struct)
     309            2 :       CALL cp_fm_create(b_re, matrix_struct)
     310            2 :       CALL cp_fm_create(b_im, matrix_struct)
     311            2 :       CALL cp_fm_create(c_re, matrix_struct)
     312            2 :       CALL cp_fm_create(c_im, matrix_struct)
     313           26 :       CALL cp_fm_set_submatrix(a_re, REAL(a, KIND=dp))
     314           26 :       CALL cp_fm_set_submatrix(a_im, AIMAG(a))
     315           26 :       CALL cp_fm_set_submatrix(b_re, REAL(b, KIND=dp))
     316           26 :       CALL cp_fm_set_submatrix(b_im, AIMAG(b))
     317              : 
     318              :       CALL cp_complex_fm_gemm('N', 'N', ndim, ndim, ndim, 1.0_dp, &
     319            2 :                               a_re, a_im, b_re, b_im, 0.0_dp, c_re, c_im)
     320            2 :       CALL cp_fm_get_submatrix(c_re, actual_re)
     321            2 :       CALL cp_fm_get_submatrix(c_im, actual_im)
     322           26 :       actual = CMPLX(actual_re, actual_im, KIND=dp)
     323           80 :       expected = MATMUL(a, b)
     324           26 :       error_nn = MAXVAL(ABS(actual - expected))
     325              : 
     326              :       CALL cp_complex_fm_gemm('C', 'N', ndim, ndim, ndim, 1.0_dp, &
     327            2 :                               a_re, a_im, b_re, b_im, 0.0_dp, c_re, c_im)
     328            2 :       CALL cp_fm_get_submatrix(c_re, actual_re)
     329            2 :       CALL cp_fm_get_submatrix(c_im, actual_im)
     330           26 :       actual = CMPLX(actual_re, actual_im, KIND=dp)
     331           80 :       expected = MATMUL(CONJG(TRANSPOSE(a)), b)
     332           26 :       error_cn = MAXVAL(ABS(actual - expected))
     333              : 
     334              :       CALL cp_complex_fm_gemm('N', 'C', ndim, ndim, ndim, 1.0_dp, &
     335            2 :                               a_re, a_im, b_re, b_im, 0.0_dp, c_re, c_im)
     336            2 :       CALL cp_fm_get_submatrix(c_re, actual_re)
     337            2 :       CALL cp_fm_get_submatrix(c_im, actual_im)
     338           26 :       actual = CMPLX(actual_re, actual_im, KIND=dp)
     339           80 :       expected = MATMUL(a, CONJG(TRANSPOSE(b)))
     340           26 :       error_nc = MAXVAL(ABS(actual - expected))
     341              : 
     342            2 :       IF (MAX(error_nn, error_cn, error_nc) > 1.0E-12_dp) nfail = nfail + 1
     343            2 :       IF (io_unit >= 0) WRITE (io_unit, '(A,3(1X,ES13.6))') &
     344            1 :          'split-complex GEMM N/N, C/N, N/C errors:', error_nn, error_cn, error_nc
     345              : 
     346            2 :       CALL cp_fm_release(c_im)
     347            2 :       CALL cp_fm_release(c_re)
     348            2 :       CALL cp_fm_release(b_im)
     349            2 :       CALL cp_fm_release(b_re)
     350            2 :       CALL cp_fm_release(a_im)
     351            2 :       CALL cp_fm_release(a_re)
     352            2 :       CALL cp_fm_struct_release(matrix_struct)
     353            2 :       CALL cp_blacs_env_release(blacs_env)
     354              : 
     355            6 :    END SUBROUTINE test_split_complex_fm_gemm
     356              : 
     357              : ! **************************************************************************************************
     358              : !> \brief Check the noncanonical complex OT energy-weighted density and its gauge covariance.
     359              : !> \param para_env parallel environment
     360              : !> \param io_unit output unit
     361              : !> \param nfail accumulated number of failures
     362              : ! **************************************************************************************************
     363            2 :    SUBROUTINE test_kpoint_ot_energy_weighted_density(para_env, io_unit, nfail)
     364              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     365              :       INTEGER, INTENT(IN)                                :: io_unit
     366              :       INTEGER, INTENT(INOUT)                             :: nfail
     367              : 
     368              :       INTEGER, PARAMETER                                 :: nao = 4, nmo = 3
     369              : 
     370              :       COMPLEX(KIND=dp), DIMENSION(nao, nao)              :: actual, expected, hmat, rotated
     371              :       COMPLEX(KIND=dp), DIMENSION(nao, nmo)              :: coeff, coeff_rot, hc_dense, hc_rot
     372              :       COMPLEX(KIND=dp), DIMENSION(nmo, nmo)              :: hblock, lagrange, rotation
     373              :       INTEGER                                            :: i, j
     374              :       REAL(KIND=dp)                                      :: error, gauge_error, hermitian_error, &
     375              :                                                             phase, pi
     376              :       REAL(KIND=dp), DIMENSION(nmo)                      :: occupation, uniform_occupation
     377              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
     378              :       TYPE(cp_cfm_type)                                  :: coeff_cfm, hc_cfm, wmat_cfm
     379              :       TYPE(cp_fm_struct_type), POINTER                   :: coeff_struct, hmat_struct
     380              : 
     381            2 :       NULLIFY (blacs_env, coeff_struct, hmat_struct)
     382            2 :       pi = ACOS(-1.0_dp)
     383            2 :       hmat(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     384            2 :       hmat(1, 1) = CMPLX(-0.8_dp, 0.0_dp, KIND=dp)
     385            2 :       hmat(2, 2) = CMPLX(0.1_dp, 0.0_dp, KIND=dp)
     386            2 :       hmat(3, 3) = CMPLX(0.7_dp, 0.0_dp, KIND=dp)
     387            2 :       hmat(4, 4) = CMPLX(1.4_dp, 0.0_dp, KIND=dp)
     388            2 :       hmat(1, 2) = CMPLX(0.21_dp, 0.13_dp, KIND=dp)
     389            2 :       hmat(1, 3) = CMPLX(-0.17_dp, 0.09_dp, KIND=dp)
     390            2 :       hmat(1, 4) = CMPLX(0.08_dp, -0.14_dp, KIND=dp)
     391            2 :       hmat(2, 3) = CMPLX(0.31_dp, -0.11_dp, KIND=dp)
     392            2 :       hmat(2, 4) = CMPLX(-0.19_dp, -0.07_dp, KIND=dp)
     393            2 :       hmat(3, 4) = CMPLX(0.27_dp, 0.16_dp, KIND=dp)
     394           10 :       DO i = 1, nao
     395           22 :          DO j = i + 1, nao
     396           20 :             hmat(j, i) = CONJG(hmat(i, j))
     397              :          END DO
     398              :       END DO
     399            8 :       DO j = 1, nmo
     400           32 :          DO i = 1, nao
     401           24 :             phase = 2.0_dp*pi*REAL((i - 1)*(j - 1), KIND=dp)/REAL(nao, KIND=dp)
     402           30 :             coeff(i, j) = CMPLX(COS(phase), SIN(phase), KIND=dp)/SQRT(REAL(nao, KIND=dp))
     403              :          END DO
     404              :       END DO
     405          128 :       hc_dense = MATMUL(hmat, coeff)
     406            2 :       occupation = [1.0_dp, 0.63_dp, 0.11_dp]
     407              : 
     408           98 :       hblock = MATMUL(CONJG(TRANSPOSE(coeff)), hc_dense)
     409            8 :       DO j = 1, nmo
     410           26 :          lagrange(:, j) = hblock(:, j)*occupation(j)
     411              :       END DO
     412           50 :       lagrange = 0.5_dp*(lagrange + CONJG(TRANSPOSE(lagrange)))
     413          226 :       expected = MATMUL(MATMUL(coeff, lagrange), CONJG(TRANSPOSE(coeff)))
     414              : 
     415            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=para_env)
     416              :       CALL cp_fm_struct_create(hmat_struct, nrow_global=nao, ncol_global=nao, &
     417            2 :                                context=blacs_env, para_env=para_env)
     418              :       CALL cp_fm_struct_create(coeff_struct, nrow_global=nao, ncol_global=nmo, &
     419            2 :                                context=blacs_env, para_env=para_env)
     420            2 :       CALL cp_cfm_create(coeff_cfm, coeff_struct)
     421            2 :       CALL cp_cfm_create(hc_cfm, coeff_struct)
     422            2 :       CALL cp_cfm_create(wmat_cfm, hmat_struct)
     423            2 :       CALL cp_cfm_set_submatrix(coeff_cfm, coeff)
     424            2 :       CALL cp_cfm_set_submatrix(hc_cfm, hc_dense)
     425              : 
     426              :       CALL kpoint_ot_energy_weighted_density( &
     427            2 :          coeff_cfm, hc_cfm, occupation, wmat_cfm)
     428            2 :       CALL cp_cfm_get_submatrix(wmat_cfm, actual)
     429           42 :       error = MAXVAL(ABS(actual - expected))
     430           42 :       hermitian_error = MAXVAL(ABS(actual - CONJG(TRANSPOSE(actual))))
     431              : 
     432            2 :       rotation(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     433            2 :       rotation(1, 1) = CMPLX(SQRT(0.5_dp), 0.0_dp, KIND=dp)
     434            2 :       rotation(1, 2) = CMPLX(0.0_dp, SQRT(0.5_dp), KIND=dp)
     435            2 :       rotation(2, 1) = CMPLX(0.0_dp, SQRT(0.5_dp), KIND=dp)
     436            2 :       rotation(2, 2) = CMPLX(SQRT(0.5_dp), 0.0_dp, KIND=dp)
     437            2 :       rotation(3, 3) = CMPLX(0.0_dp, 1.0_dp, KIND=dp)
     438           98 :       coeff_rot = MATMUL(coeff, rotation)
     439           98 :       hc_rot = MATMUL(hc_dense, rotation)
     440            8 :       uniform_occupation(:) = 1.0_dp
     441            2 :       CALL cp_cfm_set_submatrix(coeff_cfm, coeff_rot)
     442            2 :       CALL cp_cfm_set_submatrix(hc_cfm, hc_rot)
     443              :       CALL kpoint_ot_energy_weighted_density( &
     444            2 :          coeff_cfm, hc_cfm, uniform_occupation, wmat_cfm)
     445            2 :       CALL cp_cfm_get_submatrix(wmat_cfm, rotated)
     446          226 :       expected = MATMUL(MATMUL(coeff, hblock), CONJG(TRANSPOSE(coeff)))
     447           42 :       gauge_error = MAXVAL(ABS(rotated - expected))
     448              : 
     449            2 :       IF (error > 5.0E-12_dp .OR. hermitian_error > 5.0E-12_dp .OR. &
     450            0 :           gauge_error > 5.0E-12_dp) nfail = nfail + 1
     451            2 :       IF (io_unit >= 0) THEN
     452            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex OT W error/hermitian/gauge', &
     453            2 :             error, hermitian_error, gauge_error
     454              :       END IF
     455              : 
     456            2 :       CALL cp_cfm_release(wmat_cfm)
     457            2 :       CALL cp_cfm_release(hc_cfm)
     458            2 :       CALL cp_cfm_release(coeff_cfm)
     459            2 :       CALL cp_fm_struct_release(coeff_struct)
     460            2 :       CALL cp_fm_struct_release(hmat_struct)
     461            2 :       CALL cp_blacs_env_release(blacs_env)
     462              : 
     463            4 :    END SUBROUTINE test_kpoint_ot_energy_weighted_density
     464              : ! **************************************************************************************************
     465              : !> \brief Check the complex rotation norm against an analytic pair and a unitary gauge change.
     466              : !> \param io_unit output unit
     467              : !> \param nfail accumulated failures
     468              : ! **************************************************************************************************
     469            2 :    SUBROUTINE test_antihermitian_spectral_norm(io_unit, nfail)
     470              :       INTEGER, INTENT(IN)                                :: io_unit
     471              :       INTEGER, INTENT(INOUT)                             :: nfail
     472              : 
     473              :       COMPLEX(KIND=dp), DIMENSION(3)                     :: phase
     474              :       COMPLEX(KIND=dp), DIMENSION(3, 3)                  :: generator, transformed
     475              :       INTEGER                                            :: i, j
     476              :       REAL(KIND=dp)                                      :: error, norm, transformed_norm
     477              : 
     478            2 :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     479            2 :       generator(1, 2) = CMPLX(0.3_dp, 0.4_dp, KIND=dp)
     480            2 :       generator(2, 1) = -CONJG(generator(1, 2))
     481            2 :       phase = EXP(CMPLX(0.0_dp, 1.0_dp, KIND=dp)*[0.37_dp, -0.51_dp, 0.83_dp])
     482            8 :       DO j = 1, 3
     483           26 :          DO i = 1, 3
     484           24 :             transformed(i, j) = CONJG(phase(i))*generator(i, j)*phase(j)
     485              :          END DO
     486              :       END DO
     487              : 
     488            2 :       norm = qs_ot_antihermitian_spectral_norm(generator)
     489            2 :       transformed_norm = qs_ot_antihermitian_spectral_norm(transformed)
     490            2 :       error = MAX(ABS(norm - 0.5_dp), ABS(transformed_norm - norm))
     491            2 :       IF (io_unit >= 0) WRITE (io_unit, '(A,ES14.6)') &
     492            1 :          "complex rotation spectral-norm error: ", error
     493            2 :       IF (error > 1.0E-13_dp) nfail = nfail + 1
     494              : 
     495            2 :    END SUBROUTINE test_antihermitian_spectral_norm
     496              : 
     497              : ! **************************************************************************************************
     498              : !> \brief Check the fixed-N Mermin gradient, dense gauge projector, and occupation Hessian.
     499              : !> \param mynode MPI rank
     500              : !> \param nfail accumulated number of failures
     501              : ! **************************************************************************************************
     502            2 :    SUBROUTINE test_fixed_n_mermin_energy(mynode, nfail)
     503              :       INTEGER, INTENT(IN)                                :: mynode
     504              :       INTEGER, INTENT(INOUT)                             :: nfail
     505              : 
     506              :       INTEGER, PARAMETER                                 :: nbands = 4
     507              :       REAL(KIND=dp), PARAMETER                           :: fd_step = 1.0E-6_dp, maxocc = 2.0_dp, &
     508              :                                                             target_electrons = 2.25_dp, &
     509              :                                                             temperature = 0.07_dp
     510              : 
     511              :       REAL(KIND=dp)                                      :: fd_slope, gradient_error, hessian_error, &
     512              :                                                             mu, predicted_slope, response_sum, &
     513              :                                                             weighted_residual
     514              :       REAL(KIND=dp), DIMENSION(nbands) :: direction, energies, gradient, gradient_minus, &
     515              :          gradient_plus, hessian_action, occupation, rayleigh, response, response_minus, &
     516              :          response_plus, weights
     517              :       REAL(KIND=dp), DIMENSION(nbands, nbands)           :: hessian
     518              : 
     519            2 :       energies(:) = [-0.24_dp, -0.05_dp, 0.08_dp, 0.31_dp]
     520            2 :       rayleigh(:) = [-0.19_dp, -0.02_dp, 0.04_dp, 0.27_dp]
     521            2 :       weights(:) = [0.35_dp, 0.35_dp, 0.65_dp, 0.65_dp]
     522            2 :       direction(:) = [0.17_dp, -0.11_dp, 0.08_dp, -0.05_dp]
     523              : 
     524              :       CALL fixed_n_fermi_occupations(energies, weights, target_electrons, temperature, &
     525            2 :                                      maxocc, occupation, mu)
     526           10 :       response(:) = weights(:)*occupation(:)*(maxocc - occupation(:))/(maxocc*temperature)
     527           10 :       response_sum = SUM(response)
     528           10 :       weighted_residual = DOT_PRODUCT(response, rayleigh - energies)
     529              :       CALL qs_ot_fixed_n_energy_gradient(rayleigh, energies, response, response_sum, &
     530            2 :                                          weighted_residual, gradient)
     531              : 
     532              :       fd_slope = (fixed_n_mermin_value(rayleigh, energies + fd_step*direction, weights, &
     533              :                                        target_electrons, temperature, maxocc) - &
     534              :                   fixed_n_mermin_value(rayleigh, energies - fd_step*direction, weights, &
     535           18 :                                        target_electrons, temperature, maxocc))/(2.0_dp*fd_step)
     536           10 :       predicted_slope = DOT_PRODUCT(gradient, direction)
     537           10 :       gradient_error = MAX(ABS(fd_slope - predicted_slope), ABS(SUM(gradient)))
     538              : 
     539              :       ! At the canonical point h_i=e_i, the exact local Hessian is the dense fixed-N projector.
     540            2 :       rayleigh(:) = energies(:)
     541            2 :       CALL qs_ot_fixed_n_energy_hessian(response, response_sum, hessian)
     542           42 :       hessian_action(:) = MATMUL(hessian, direction)
     543              : 
     544              :       CALL fixed_n_fermi_occupations(energies + fd_step*direction, weights, target_electrons, &
     545           10 :                                      temperature, maxocc, occupation, mu)
     546           10 :       response_plus(:) = weights(:)*occupation(:)*(maxocc - occupation(:))/(maxocc*temperature)
     547           10 :       weighted_residual = DOT_PRODUCT(response_plus, rayleigh - energies - fd_step*direction)
     548              :       CALL qs_ot_fixed_n_energy_gradient(rayleigh, energies + fd_step*direction, response_plus, &
     549           18 :                                          SUM(response_plus), weighted_residual, gradient_plus)
     550              : 
     551              :       CALL fixed_n_fermi_occupations(energies - fd_step*direction, weights, target_electrons, &
     552           10 :                                      temperature, maxocc, occupation, mu)
     553           10 :       response_minus(:) = weights(:)*occupation(:)*(maxocc - occupation(:))/(maxocc*temperature)
     554           10 :       weighted_residual = DOT_PRODUCT(response_minus, rayleigh - energies + fd_step*direction)
     555              :       CALL qs_ot_fixed_n_energy_gradient(rayleigh, energies - fd_step*direction, response_minus, &
     556           18 :                                          SUM(response_minus), weighted_residual, gradient_minus)
     557              : 
     558              :       hessian_error = MAXVAL(ABS((gradient_plus - gradient_minus)/(2.0_dp*fd_step) - &
     559           10 :                                  hessian_action))
     560           42 :       hessian_error = MAX(hessian_error, MAXVAL(ABS(hessian - TRANSPOSE(hessian))))
     561           52 :       hessian_error = MAX(hessian_error, MAXVAL(ABS(MATMUL(hessian, SPREAD(1.0_dp, 1, nbands)))))
     562              : 
     563            2 :       IF (gradient_error > 2.0E-8_dp .OR. hessian_error > 2.0E-7_dp) nfail = nfail + 1
     564            2 :       IF (mynode == 0) THEN
     565            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') "fixed-N Mermin gradient/Hessian errors:", &
     566            2 :             gradient_error, hessian_error
     567              :       END IF
     568              : 
     569            2 :    END SUBROUTINE test_fixed_n_mermin_energy
     570              : 
     571              : ! **************************************************************************************************
     572              : !> \brief Check the complex fixed-N spectral occupation-projector derivative by finite differences.
     573              : !> \param mynode MPI rank
     574              : !> \param nfail accumulated number of failures
     575              : ! **************************************************************************************************
     576            2 :    SUBROUTINE test_fixed_n_projector_frechet(mynode, nfail)
     577              :       INTEGER, INTENT(IN)                                :: mynode
     578              :       INTEGER, INTENT(INOUT)                             :: nfail
     579              : 
     580              :       INTEGER, PARAMETER                                 :: nbands = 3
     581              :       REAL(KIND=dp), PARAMETER :: fd_step = 1.0E-6_dp, kpoint_weight = 0.75_dp, maxocc = 2.0_dp, &
     582              :          target_electrons = 2.10_dp, temperature = 0.09_dp
     583              : 
     584              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands)        :: dchc, derivative, derivative_fd, &
     585              :                                                             hamiltonian, projector_minus, &
     586              :                                                             projector_plus, vectors
     587              :       INTEGER                                            :: i
     588              :       REAL(KIND=dp)                                      :: error, mu, trace_error
     589              :       REAL(KIND=dp), DIMENSION(nbands)                   :: eigenvalues, occupation, response, &
     590              :                                                             weights
     591              : 
     592            2 :       hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     593            2 :       hamiltonian(1, 1) = CMPLX(-0.22_dp, 0.0_dp, KIND=dp)
     594            2 :       hamiltonian(2, 2) = CMPLX(0.03_dp, 0.0_dp, KIND=dp)
     595            2 :       hamiltonian(3, 3) = CMPLX(0.28_dp, 0.0_dp, KIND=dp)
     596            2 :       hamiltonian(1, 2) = CMPLX(0.04_dp, -0.03_dp, KIND=dp)
     597            2 :       hamiltonian(2, 1) = CONJG(hamiltonian(1, 2))
     598            2 :       hamiltonian(2, 3) = CMPLX(-0.02_dp, 0.05_dp, KIND=dp)
     599            2 :       hamiltonian(3, 2) = CONJG(hamiltonian(2, 3))
     600              : 
     601            2 :       dchc(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     602            2 :       dchc(1, 1) = CMPLX(0.13_dp, 0.0_dp, KIND=dp)
     603            2 :       dchc(2, 2) = CMPLX(-0.08_dp, 0.0_dp, KIND=dp)
     604            2 :       dchc(3, 3) = CMPLX(0.05_dp, 0.0_dp, KIND=dp)
     605            2 :       dchc(1, 2) = CMPLX(0.17_dp, -0.11_dp, KIND=dp)
     606            2 :       dchc(2, 1) = CONJG(dchc(1, 2))
     607            2 :       dchc(2, 3) = CMPLX(0.12_dp, 0.09_dp, KIND=dp)
     608            2 :       dchc(3, 2) = CONJG(dchc(2, 3))
     609              : 
     610            2 :       CALL diag_complex(hamiltonian, vectors, eigenvalues)
     611            8 :       weights(:) = kpoint_weight
     612              :       CALL fixed_n_fermi_occupations(eigenvalues, weights, target_electrons, temperature, &
     613            2 :                                      maxocc, occupation, mu)
     614            8 :       response(:) = weights(:)*occupation(:)*(maxocc - occupation(:))/(maxocc*temperature)
     615              :       CALL qs_ot_fixed_n_projector_frechet(hamiltonian, dchc, occupation, kpoint_weight, &
     616            8 :                                            response, SUM(response), derivative)
     617              : 
     618              :       CALL fixed_n_spectral_projector(hamiltonian + fd_step*dchc, weights, target_electrons, &
     619           26 :                                       temperature, maxocc, projector_plus)
     620              :       CALL fixed_n_spectral_projector(hamiltonian - fd_step*dchc, weights, target_electrons, &
     621           26 :                                       temperature, maxocc, projector_minus)
     622           26 :       derivative_fd(:, :) = (projector_plus - projector_minus)/(2.0_dp*fd_step)
     623           26 :       error = MAXVAL(ABS(derivative - derivative_fd))
     624            2 :       trace_error = 0.0_dp
     625            8 :       DO i = 1, nbands
     626            8 :          trace_error = trace_error + REAL(derivative(i, i), KIND=dp)
     627              :       END DO
     628            2 :       error = MAX(error, ABS(trace_error))
     629              : 
     630            2 :       IF (error > 3.0E-7_dp) nfail = nfail + 1
     631            2 :       IF (mynode == 0) WRITE (io_unit, '(A,1X,ES13.6)') &
     632            1 :          "fixed-N complex occupation-projector error:", error
     633              : 
     634            2 :    END SUBROUTINE test_fixed_n_projector_frechet
     635              : 
     636              : ! **************************************************************************************************
     637              : !> \brief Check the finite complex REF rotation Hessian against the scalar band energy.
     638              : !> \param mynode MPI rank
     639              : !> \param nfail accumulated number of failures
     640              : ! **************************************************************************************************
     641            2 :    SUBROUTINE test_finite_rotation_response(mynode, nfail)
     642              :       INTEGER, INTENT(IN)                                :: mynode
     643              :       INTEGER, INTENT(INOUT)                             :: nfail
     644              : 
     645              :       INTEGER, PARAMETER                                 :: nbands = 3, nrotation = 6
     646              :       REAL(KIND=dp), PARAMETER                           :: fd_step = 2.0E-4_dp, &
     647              :                                                             kpoint_weight = 0.625_dp
     648              : 
     649              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands)        :: base_hamiltonian, chc, &
     650              :                                                             direction_generator, generator, &
     651              :                                                             rotation, rotation_minus, rotation_plus
     652              :       INTEGER                                            :: i
     653              :       REAL(KIND=dp)                                      :: energy, energy_minus, energy_plus, &
     654              :                                                             gradient_error, hessian_error, &
     655              :                                                             rayleigh_error, symmetry_error
     656              :       REAL(KIND=dp), DIMENSION(nbands)                   :: occupation, rayleigh_minus, rayleigh_plus
     657              :       REAL(KIND=dp), DIMENSION(nbands, nrotation)        :: rayleigh_response
     658              :       REAL(KIND=dp), DIMENSION(nrotation)                :: direction, gradient
     659              :       REAL(KIND=dp), DIMENSION(nrotation, nrotation)     :: hessian
     660              : 
     661            2 :       base_hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     662            2 :       base_hamiltonian(1, 1) = CMPLX(-0.31_dp, 0.0_dp, KIND=dp)
     663            2 :       base_hamiltonian(2, 2) = CMPLX(0.07_dp, 0.0_dp, KIND=dp)
     664            2 :       base_hamiltonian(3, 3) = CMPLX(0.42_dp, 0.0_dp, KIND=dp)
     665            2 :       base_hamiltonian(1, 2) = CMPLX(0.09_dp, -0.06_dp, KIND=dp)
     666            2 :       base_hamiltonian(1, 3) = CMPLX(-0.04_dp, 0.08_dp, KIND=dp)
     667            2 :       base_hamiltonian(2, 3) = CMPLX(0.11_dp, 0.05_dp, KIND=dp)
     668            8 :       DO i = 1, nbands
     669           14 :          base_hamiltonian(i, 1:i - 1) = CONJG(base_hamiltonian(1:i - 1, i))
     670              :       END DO
     671              : 
     672            2 :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     673            2 :       generator(1, 2) = CMPLX(0.17_dp, -0.08_dp, KIND=dp)
     674            2 :       generator(1, 3) = CMPLX(-0.09_dp, 0.04_dp, KIND=dp)
     675            2 :       generator(2, 3) = CMPLX(0.12_dp, 0.07_dp, KIND=dp)
     676            8 :       DO i = 1, nbands
     677           14 :          generator(i, 1:i - 1) = -CONJG(generator(1:i - 1, i))
     678              :       END DO
     679              : 
     680            2 :       direction(:) = [0.14_dp, -0.07_dp, -0.11_dp, 0.09_dp, 0.05_dp, 0.13_dp]
     681            2 :       direction_generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     682            2 :       direction_generator(1, 2) = CMPLX(direction(1), direction(2), KIND=dp)
     683            2 :       direction_generator(2, 1) = -CONJG(direction_generator(1, 2))
     684            2 :       direction_generator(1, 3) = CMPLX(direction(3), direction(4), KIND=dp)
     685            2 :       direction_generator(3, 1) = -CONJG(direction_generator(1, 3))
     686            2 :       direction_generator(2, 3) = CMPLX(direction(5), direction(6), KIND=dp)
     687            2 :       direction_generator(3, 2) = -CONJG(direction_generator(2, 3))
     688            2 :       occupation(:) = [1.73_dp, 0.88_dp, 0.19_dp]
     689              : 
     690            2 :       rotation(:, :) = dense_antihermitian_exp(generator)
     691              :       chc(:, :) = MATMUL(CONJG(TRANSPOSE(rotation)), &
     692          158 :                          MATMUL(base_hamiltonian, rotation))
     693              :       CALL qs_ot_finite_rotation_response(chc, generator, occupation, kpoint_weight, &
     694            2 :                                           gradient, hessian, rayleigh_response)
     695              : 
     696           26 :       rotation_plus(:, :) = dense_antihermitian_exp(generator + fd_step*direction_generator)
     697           26 :       rotation_minus(:, :) = dense_antihermitian_exp(generator - fd_step*direction_generator)
     698            2 :       energy = finite_rotation_energy(rotation, base_hamiltonian, occupation, kpoint_weight)
     699            2 :       energy_plus = finite_rotation_energy(rotation_plus, base_hamiltonian, occupation, kpoint_weight)
     700            2 :       energy_minus = finite_rotation_energy(rotation_minus, base_hamiltonian, occupation, kpoint_weight)
     701              :       gradient_error = ABS((energy_plus - energy_minus)/(2.0_dp*fd_step) - &
     702           14 :                            DOT_PRODUCT(gradient, direction))
     703              :       hessian_error = ABS((energy_plus - 2.0_dp*energy + energy_minus)/fd_step**2 - &
     704           98 :                           DOT_PRODUCT(direction, MATMUL(hessian, direction)))
     705           86 :       symmetry_error = MAXVAL(ABS(hessian - TRANSPOSE(hessian)))
     706              : 
     707            8 :       DO i = 1, nbands
     708              :          rayleigh_plus(i) = REAL(DOT_PRODUCT(rotation_plus(:, i), &
     709           96 :                                              MATMUL(base_hamiltonian, rotation_plus(:, i))), KIND=dp)
     710              :          rayleigh_minus(i) = REAL(DOT_PRODUCT(rotation_minus(:, i), &
     711           98 :                                               MATMUL(base_hamiltonian, rotation_minus(:, i))), KIND=dp)
     712              :       END DO
     713              :       rayleigh_error = MAXVAL(ABS((rayleigh_plus - rayleigh_minus)/(2.0_dp*fd_step) - &
     714           56 :                                   MATMUL(rayleigh_response, direction)))
     715              : 
     716              :       IF (gradient_error > 5.0E-8_dp .OR. hessian_error > 2.0E-6_dp .OR. &
     717            2 :           rayleigh_error > 2.0E-7_dp .OR. symmetry_error > 1.0E-12_dp) nfail = nfail + 1
     718            2 :       IF (mynode == 0) THEN
     719            1 :          WRITE (io_unit, '(A,4(1X,ES13.6))') "finite complex rotation response errors:", &
     720            2 :             gradient_error, hessian_error, rayleigh_error, symmetry_error
     721              :       END IF
     722              : 
     723            2 :    END SUBROUTINE test_finite_rotation_response
     724              : 
     725              : ! **************************************************************************************************
     726              : !> \brief Check the coupled fixed-N rotation/energy Hessian and its Schur decomposition.
     727              : !> \param mynode MPI rank
     728              : !> \param nfail accumulated number of failures
     729              : ! **************************************************************************************************
     730            2 :    SUBROUTINE test_fixed_n_rotation_schur(mynode, nfail)
     731              :       INTEGER, INTENT(IN)                                :: mynode
     732              :       INTEGER, INTENT(INOUT)                             :: nfail
     733              : 
     734              :       INTEGER, PARAMETER                                 :: nbands = 3, nrotation = 6
     735              :       REAL(KIND=dp), PARAMETER                           :: fd_step = 1.0E-4_dp, &
     736              :                                                             kpoint_weight = 0.625_dp, &
     737              :                                                             maxocc = 2.0_dp, temperature = 0.08_dp
     738              : 
     739              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands)        :: base_hamiltonian, chc, &
     740              :                                                             direction_generator, generator, &
     741              :                                                             rotation, rotation_minus, rotation_plus
     742              :       INTEGER                                            :: i
     743              :       REAL(KIND=dp)                                      :: curvature, curvature_error, &
     744              :                                                             energy_minus, energy_plus, &
     745              :                                                             energy_zero, mu, response_sum, &
     746              :                                                             schur_error
     747              :       REAL(KIND=dp), DIMENSION(nbands) :: energy_coordinate, energy_direction, energy_gradient, &
     748              :          energy_minus_coordinate, energy_plus_coordinate, occupation, rayleigh, rayleigh_minus, &
     749              :          rayleigh_plus, response, weights
     750              :       REAL(KIND=dp), DIMENSION(nbands, nbands)           :: energy_hessian
     751              :       REAL(KIND=dp), DIMENSION(nbands, nrotation)        :: rayleigh_response
     752              :       REAL(KIND=dp), DIMENSION(nrotation)                :: coupling, rotation_direction, &
     753              :                                                             rotation_gradient, schur_rhs
     754              :       REAL(KIND=dp), DIMENSION(nrotation, nrotation)     :: rotation_hessian, schur, schur_block
     755              : 
     756            2 :       base_hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     757            2 :       base_hamiltonian(1, 1) = CMPLX(-0.31_dp, 0.0_dp, KIND=dp)
     758            2 :       base_hamiltonian(2, 2) = CMPLX(0.07_dp, 0.0_dp, KIND=dp)
     759            2 :       base_hamiltonian(3, 3) = CMPLX(0.42_dp, 0.0_dp, KIND=dp)
     760            2 :       base_hamiltonian(1, 2) = CMPLX(0.09_dp, -0.06_dp, KIND=dp)
     761            2 :       base_hamiltonian(1, 3) = CMPLX(-0.04_dp, 0.08_dp, KIND=dp)
     762            2 :       base_hamiltonian(2, 3) = CMPLX(0.11_dp, 0.05_dp, KIND=dp)
     763            8 :       DO i = 1, nbands
     764           14 :          base_hamiltonian(i, 1:i - 1) = CONJG(base_hamiltonian(1:i - 1, i))
     765              :       END DO
     766              : 
     767            2 :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     768            2 :       generator(1, 2) = CMPLX(0.17_dp, -0.08_dp, KIND=dp)
     769            2 :       generator(1, 3) = CMPLX(-0.09_dp, 0.04_dp, KIND=dp)
     770            2 :       generator(2, 3) = CMPLX(0.12_dp, 0.07_dp, KIND=dp)
     771            8 :       DO i = 1, nbands
     772           14 :          generator(i, 1:i - 1) = -CONJG(generator(1:i - 1, i))
     773              :       END DO
     774            2 :       rotation_direction(:) = [0.14_dp, -0.07_dp, -0.11_dp, 0.09_dp, 0.05_dp, 0.13_dp]
     775            2 :       energy_direction(:) = [0.08_dp, -0.11_dp, 0.03_dp]
     776            2 :       direction_generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     777            2 :       direction_generator(1, 2) = CMPLX(rotation_direction(1), rotation_direction(2), KIND=dp)
     778            2 :       direction_generator(2, 1) = -CONJG(direction_generator(1, 2))
     779            2 :       direction_generator(1, 3) = CMPLX(rotation_direction(3), rotation_direction(4), KIND=dp)
     780            2 :       direction_generator(3, 1) = -CONJG(direction_generator(1, 3))
     781            2 :       direction_generator(2, 3) = CMPLX(rotation_direction(5), rotation_direction(6), KIND=dp)
     782            2 :       direction_generator(3, 2) = -CONJG(direction_generator(2, 3))
     783              : 
     784            2 :       rotation(:, :) = dense_antihermitian_exp(generator)
     785          158 :       chc(:, :) = MATMUL(CONJG(TRANSPOSE(rotation)), MATMUL(base_hamiltonian, rotation))
     786            8 :       DO i = 1, nbands
     787            8 :          rayleigh(i) = REAL(chc(i, i), KIND=dp)
     788              :       END DO
     789            2 :       energy_coordinate(:) = rayleigh(:)
     790            8 :       weights(:) = kpoint_weight
     791              :       CALL fixed_n_fermi_occupations(energy_coordinate, weights, 1.7_dp, temperature, &
     792            2 :                                      maxocc, occupation, mu)
     793            8 :       response(:) = weights*occupation*(maxocc - occupation)/(maxocc*temperature)
     794            8 :       response_sum = SUM(response)
     795              :       CALL qs_ot_finite_rotation_response(chc, generator, occupation, kpoint_weight, &
     796            2 :                                           rotation_gradient, rotation_hessian, rayleigh_response)
     797              :       CALL qs_ot_fixed_n_energy_gradient(rayleigh, energy_coordinate, response, response_sum, &
     798            2 :                                          0.0_dp, energy_gradient)
     799            2 :       CALL qs_ot_fixed_n_energy_hessian(response, response_sum, energy_hessian)
     800              :       CALL qs_ot_fixed_n_schur_block( &
     801              :          rotation_hessian, rayleigh_response, response, rotation_gradient, energy_gradient, &
     802            2 :          schur_block, coupling, schur_rhs)
     803              :       schur(:, :) = schur_block + SPREAD(coupling, DIM=2, NCOPIES=nrotation)* &
     804           86 :                     SPREAD(coupling, DIM=1, NCOPIES=nrotation)/response_sum
     805              :       schur_error = MAXVAL(ABS(schur - rotation_hessian + &
     806              :                                MATMUL(TRANSPOSE(rayleigh_response), &
     807          544 :                                       MATMUL(energy_hessian, rayleigh_response))))
     808              : 
     809           26 :       rotation_plus(:, :) = dense_antihermitian_exp(generator + fd_step*direction_generator)
     810           26 :       rotation_minus(:, :) = dense_antihermitian_exp(generator - fd_step*direction_generator)
     811            8 :       DO i = 1, nbands
     812              :          rayleigh_plus(i) = REAL(DOT_PRODUCT(rotation_plus(:, i), &
     813           96 :                                              MATMUL(base_hamiltonian, rotation_plus(:, i))), KIND=dp)
     814              :          rayleigh_minus(i) = REAL(DOT_PRODUCT(rotation_minus(:, i), &
     815          104 :                                               MATMUL(base_hamiltonian, rotation_minus(:, i))), KIND=dp)
     816              :       END DO
     817            8 :       energy_plus_coordinate(:) = energy_coordinate + fd_step*energy_direction
     818            8 :       energy_minus_coordinate(:) = energy_coordinate - fd_step*energy_direction
     819              :       energy_zero = fixed_n_mermin_value(rayleigh, energy_coordinate, weights, 1.7_dp, &
     820            2 :                                          temperature, maxocc)
     821              :       energy_plus = fixed_n_mermin_value(rayleigh_plus, energy_plus_coordinate, weights, 1.7_dp, &
     822            2 :                                          temperature, maxocc)
     823              :       energy_minus = fixed_n_mermin_value(rayleigh_minus, energy_minus_coordinate, weights, 1.7_dp, &
     824            2 :                                           temperature, maxocc)
     825              :       curvature = DOT_PRODUCT(rotation_direction, MATMUL(rotation_hessian, rotation_direction)) - &
     826              :                   2.0_dp*DOT_PRODUCT(energy_direction, &
     827              :                                      MATMUL(energy_hessian, &
     828              :                                             MATMUL(rayleigh_response, rotation_direction))) + &
     829          212 :                   DOT_PRODUCT(energy_direction, MATMUL(energy_hessian, energy_direction))
     830            2 :       curvature_error = ABS((energy_plus - 2.0_dp*energy_zero + energy_minus)/fd_step**2 - curvature)
     831              : 
     832            2 :       IF (schur_error > 2.0E-11_dp .OR. curvature_error > 3.0E-6_dp) nfail = nfail + 1
     833            2 :       IF (mynode == 0) THEN
     834            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') "fixed-N rotation Schur errors:", &
     835            2 :             schur_error, curvature_error
     836              :       END IF
     837              : 
     838            2 :    END SUBROUTINE test_fixed_n_rotation_schur
     839              : 
     840              : ! **************************************************************************************************
     841              : !> \brief verify the two-chemical-potential Schur reduction used by shared ROKS orbitals
     842              : !> \param mynode MPI rank
     843              : !> \param nfail accumulated failures
     844              : ! **************************************************************************************************
     845            2 :    SUBROUTINE test_fixed_n_multigroup_schur(mynode, nfail)
     846              :       INTEGER, INTENT(IN)                                :: mynode
     847              :       INTEGER, INTENT(INOUT)                             :: nfail
     848              : 
     849              :       INTEGER, PARAMETER                                 :: nenergy = 4, ngroups = 2, nrotation = 3
     850              : 
     851              :       INTEGER, DIMENSION(nenergy)                        :: response_group
     852              :       REAL(KIND=dp)                                      :: coupling_error, rhs_error, schur_error
     853              :       REAL(KIND=dp), DIMENSION(nenergy)                  :: energy_gradient, response_weight
     854              :       REAL(KIND=dp), DIMENSION(nenergy, nrotation)       :: rayleigh_response
     855              :       REAL(KIND=dp), DIMENSION(nrotation)                :: rotation_gradient, schur_rhs
     856              :       REAL(KIND=dp), DIMENSION(nrotation, ngroups)       :: coupling
     857              :       REAL(KIND=dp), DIMENSION(nrotation, nrotation)     :: expected, rotation_hessian, schur_block
     858              : 
     859              :       rotation_hessian(:, :) = RESHAPE( &
     860              :                                [2.3_dp, 0.2_dp, -0.1_dp, 0.2_dp, 1.8_dp, 0.3_dp, -0.1_dp, 0.3_dp, 1.5_dp], &
     861            2 :                                [nrotation, nrotation])
     862              :       rayleigh_response(:, :) = RESHAPE( &
     863              :                                 [0.4_dp, -0.2_dp, 0.1_dp, 0.3_dp, &
     864              :                                  -0.1_dp, 0.5_dp, 0.2_dp, -0.4_dp, &
     865              :                                  0.3_dp, 0.1_dp, -0.5_dp, 0.2_dp], &
     866            2 :                                 [nenergy, nrotation])
     867            2 :       response_weight(:) = [0.7_dp, 0.5_dp, 0.4_dp, 0.2_dp]
     868            2 :       response_group(:) = [1, 1, 2, 2]
     869            2 :       rotation_gradient(:) = [0.2_dp, -0.1_dp, 0.3_dp]
     870            2 :       energy_gradient(:) = [0.04_dp, -0.03_dp, 0.02_dp, -0.01_dp]
     871              : 
     872              :       CALL qs_ot_fixed_n_multigroup_schur_block( &
     873              :          rotation_hessian, rayleigh_response, response_weight, response_group, &
     874            2 :          rotation_gradient, energy_gradient, schur_block, coupling, schur_rhs)
     875              : 
     876              :       expected(:, :) = rotation_hessian - &
     877              :                        MATMUL(TRANSPOSE(rayleigh_response), &
     878              :                               MATMUL(RESHAPE( &
     879              :                                      [0.2916666666666667_dp, -0.2916666666666667_dp, 0.0_dp, 0.0_dp, &
     880              :                                       -0.2916666666666667_dp, 0.2916666666666667_dp, 0.0_dp, 0.0_dp, &
     881              :                                       0.0_dp, 0.0_dp, 0.1333333333333333_dp, -0.1333333333333333_dp, &
     882              :                                       0.0_dp, 0.0_dp, -0.1333333333333333_dp, 0.1333333333333333_dp], &
     883          250 :                                      [nenergy, nenergy]), rayleigh_response))
     884              :       schur_error = MAXVAL(ABS( &
     885              :                            schur_block + &
     886              :                            MATMUL(coupling(:, 1:1), TRANSPOSE(coupling(:, 1:1)))/1.2_dp + &
     887           88 :                            MATMUL(coupling(:, 2:2), TRANSPOSE(coupling(:, 2:2)))/0.6_dp - expected))
     888              :       coupling_error = MAXVAL(ABS(coupling(:, 1) - &
     889              :                                   MATMUL(TRANSPOSE(rayleigh_response(1:2, :)), &
     890           10 :                                          response_weight(1:2))))
     891              :       coupling_error = MAX(coupling_error, MAXVAL(ABS(coupling(:, 2) - &
     892              :                                                       MATMUL(TRANSPOSE(rayleigh_response(3:4, :)), &
     893           10 :                                                              response_weight(3:4)))))
     894              :       rhs_error = MAXVAL(ABS(schur_rhs - rotation_gradient - &
     895           10 :                              MATMUL(TRANSPOSE(rayleigh_response), energy_gradient)))
     896              : 
     897            2 :       IF (MAX(schur_error, coupling_error, rhs_error) > 2.0E-14_dp) nfail = nfail + 1
     898            2 :       IF (mynode == 0) THEN
     899            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') "fixed-N multigroup Schur errors:", &
     900            2 :             schur_error, coupling_error, rhs_error
     901              :       END IF
     902              : 
     903            2 :    END SUBROUTINE test_fixed_n_multigroup_schur
     904              : 
     905              : ! **************************************************************************************************
     906              : !> \brief finite-difference the shared-orbital, two-fixed-N ROKS Mermin Hessian
     907              : !> \param mynode MPI rank
     908              : !> \param nfail accumulated failures
     909              : ! **************************************************************************************************
     910            2 :    SUBROUTINE test_roks_fixed_n_rotation_schur(mynode, nfail)
     911              :       INTEGER, INTENT(IN)                                :: mynode
     912              :       INTEGER, INTENT(INOUT)                             :: nfail
     913              : 
     914              :       INTEGER, PARAMETER                                 :: nbands = 3, nenergy = 6, ngroups = 2, &
     915              :                                                             nrotation = 6
     916              :       REAL(KIND=dp), PARAMETER                           :: fd_step = 1.0E-4_dp, &
     917              :                                                             kpoint_weight = 0.7_dp, &
     918              :                                                             maxocc = 1.0_dp, temperature = 0.09_dp
     919              : 
     920              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands) :: chc_a, chc_b, direction_generator, generator, &
     921              :          hamiltonian_a, hamiltonian_b, rotation, rotation_minus, rotation_plus
     922              :       INTEGER                                            :: i
     923              :       INTEGER, DIMENSION(nenergy)                        :: response_group
     924              :       REAL(KIND=dp) :: curvature, curvature_a, curvature_b, curvature_error, energy_minus, &
     925              :          energy_minus_a, energy_minus_b, energy_plus, energy_plus_a, energy_plus_b, energy_zero, &
     926              :          energy_zero_a, energy_zero_b, mu_a, mu_b, response_sum_a, response_sum_b, schur_error
     927              :       REAL(KIND=dp), DIMENSION(nbands) :: energy_a, energy_a_minus, energy_a_plus, energy_b, &
     928              :          energy_b_minus, energy_b_plus, occupation_a, occupation_b, rayleigh_a, rayleigh_a_minus, &
     929              :          rayleigh_a_plus, rayleigh_b, rayleigh_b_minus, rayleigh_b_plus, response_a, response_b, &
     930              :          weights
     931              :       REAL(KIND=dp), DIMENSION(nbands, nrotation)        :: spin_rayleigh_response
     932              :       REAL(KIND=dp), DIMENSION(nenergy)                  :: energy_direction, energy_gradient, &
     933              :                                                             response
     934              :       REAL(KIND=dp), DIMENSION(nenergy, nenergy)         :: energy_hessian
     935              :       REAL(KIND=dp), DIMENSION(nenergy, nrotation)       :: rayleigh_response
     936              :       REAL(KIND=dp), DIMENSION(nrotation)                :: rotation_direction, rotation_gradient, &
     937              :                                                             schur_rhs, spin_gradient
     938              :       REAL(KIND=dp), DIMENSION(nrotation, ngroups)       :: coupling
     939              :       REAL(KIND=dp), DIMENSION(nrotation, nrotation)     :: rotation_hessian, rotation_hessian_a, &
     940              :                                                             rotation_hessian_b, schur, &
     941              :                                                             schur_block, spin_hessian
     942              : 
     943            2 :       hamiltonian_a(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     944            2 :       hamiltonian_b(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     945            2 :       hamiltonian_a(1, 1) = -0.42_dp
     946            2 :       hamiltonian_a(2, 2) = 0.03_dp
     947            2 :       hamiltonian_a(3, 3) = 0.37_dp
     948            2 :       hamiltonian_a(1, 2) = CMPLX(0.08_dp, -0.05_dp, KIND=dp)
     949            2 :       hamiltonian_a(1, 3) = CMPLX(-0.03_dp, 0.07_dp, KIND=dp)
     950            2 :       hamiltonian_a(2, 3) = CMPLX(0.09_dp, 0.04_dp, KIND=dp)
     951            2 :       hamiltonian_b(1, 1) = -0.31_dp
     952            2 :       hamiltonian_b(2, 2) = 0.11_dp
     953            2 :       hamiltonian_b(3, 3) = 0.46_dp
     954            2 :       hamiltonian_b(1, 2) = CMPLX(-0.06_dp, 0.02_dp, KIND=dp)
     955            2 :       hamiltonian_b(1, 3) = CMPLX(0.05_dp, 0.04_dp, KIND=dp)
     956            2 :       hamiltonian_b(2, 3) = CMPLX(-0.07_dp, 0.06_dp, KIND=dp)
     957            8 :       DO i = 1, nbands
     958           12 :          hamiltonian_a(i, 1:i - 1) = CONJG(hamiltonian_a(1:i - 1, i))
     959           14 :          hamiltonian_b(i, 1:i - 1) = CONJG(hamiltonian_b(1:i - 1, i))
     960              :       END DO
     961              : 
     962            2 :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     963            2 :       generator(1, 2) = CMPLX(0.14_dp, -0.06_dp, KIND=dp)
     964            2 :       generator(1, 3) = CMPLX(-0.08_dp, 0.05_dp, KIND=dp)
     965            2 :       generator(2, 3) = CMPLX(0.10_dp, 0.03_dp, KIND=dp)
     966            8 :       DO i = 1, nbands
     967           14 :          generator(i, 1:i - 1) = -CONJG(generator(1:i - 1, i))
     968              :       END DO
     969            2 :       rotation_direction(:) = [0.09_dp, -0.04_dp, -0.07_dp, 0.06_dp, 0.03_dp, 0.08_dp]
     970            2 :       energy_direction(:) = [0.05_dp, -0.04_dp, 0.02_dp, -0.03_dp, 0.06_dp, -0.01_dp]
     971            2 :       direction_generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
     972            2 :       direction_generator(1, 2) = CMPLX(rotation_direction(1), rotation_direction(2), KIND=dp)
     973            2 :       direction_generator(2, 1) = -CONJG(direction_generator(1, 2))
     974            2 :       direction_generator(1, 3) = CMPLX(rotation_direction(3), rotation_direction(4), KIND=dp)
     975            2 :       direction_generator(3, 1) = -CONJG(direction_generator(1, 3))
     976            2 :       direction_generator(2, 3) = CMPLX(rotation_direction(5), rotation_direction(6), KIND=dp)
     977            2 :       direction_generator(3, 2) = -CONJG(direction_generator(2, 3))
     978              : 
     979            2 :       rotation(:, :) = dense_antihermitian_exp(generator)
     980          158 :       chc_a(:, :) = MATMUL(CONJG(TRANSPOSE(rotation)), MATMUL(hamiltonian_a, rotation))
     981          158 :       chc_b(:, :) = MATMUL(CONJG(TRANSPOSE(rotation)), MATMUL(hamiltonian_b, rotation))
     982            8 :       DO i = 1, nbands
     983            6 :          rayleigh_a(i) = REAL(chc_a(i, i), KIND=dp)
     984            8 :          rayleigh_b(i) = REAL(chc_b(i, i), KIND=dp)
     985              :       END DO
     986            2 :       energy_a(:) = rayleigh_a
     987            2 :       energy_b(:) = rayleigh_b
     988            8 :       weights(:) = kpoint_weight
     989              :       CALL fixed_n_fermi_occupations(energy_a, weights, 1.65_dp, temperature, &
     990            2 :                                      maxocc, occupation_a, mu_a)
     991              :       CALL fixed_n_fermi_occupations(energy_b, weights, 0.95_dp, temperature, &
     992            2 :                                      maxocc, occupation_b, mu_b)
     993            8 :       response_a(:) = weights*occupation_a*(maxocc - occupation_a)/(maxocc*temperature)
     994            8 :       response_b(:) = weights*occupation_b*(maxocc - occupation_b)/(maxocc*temperature)
     995            8 :       response_sum_a = SUM(response_a)
     996            8 :       response_sum_b = SUM(response_b)
     997            8 :       response(1:nbands) = response_a
     998            8 :       response(nbands + 1:nenergy) = response_b
     999            2 :       response_group(:) = [1, 1, 1, 2, 2, 2]
    1000              : 
    1001              :       CALL qs_ot_finite_rotation_response( &
    1002              :          chc_a, generator, occupation_a, kpoint_weight, spin_gradient, spin_hessian, &
    1003            2 :          spin_rayleigh_response)
    1004            2 :       rotation_gradient(:) = spin_gradient
    1005            2 :       rotation_hessian_a(:, :) = spin_hessian
    1006           50 :       rayleigh_response(1:nbands, :) = spin_rayleigh_response
    1007              :       CALL qs_ot_finite_rotation_response( &
    1008              :          chc_b, generator, occupation_b, kpoint_weight, spin_gradient, spin_hessian, &
    1009            2 :          spin_rayleigh_response)
    1010           14 :       rotation_gradient(:) = rotation_gradient + spin_gradient
    1011            2 :       rotation_hessian_b(:, :) = spin_hessian
    1012           86 :       rotation_hessian(:, :) = rotation_hessian_a + rotation_hessian_b
    1013           50 :       rayleigh_response(nbands + 1:nenergy, :) = spin_rayleigh_response
    1014              : 
    1015              :       CALL qs_ot_fixed_n_energy_gradient( &
    1016              :          rayleigh_a, energy_a, response_a, response_sum_a, 0.0_dp, &
    1017            2 :          energy_gradient(1:nbands))
    1018              :       CALL qs_ot_fixed_n_energy_gradient( &
    1019              :          rayleigh_b, energy_b, response_b, response_sum_b, 0.0_dp, &
    1020            2 :          energy_gradient(nbands + 1:nenergy))
    1021            2 :       energy_hessian(:, :) = 0.0_dp
    1022              :       CALL qs_ot_fixed_n_energy_hessian( &
    1023            2 :          response_a, response_sum_a, energy_hessian(1:nbands, 1:nbands))
    1024              :       CALL qs_ot_fixed_n_energy_hessian( &
    1025            2 :          response_b, response_sum_b, energy_hessian(nbands + 1:nenergy, nbands + 1:nenergy))
    1026              :       CALL qs_ot_fixed_n_multigroup_schur_block( &
    1027              :          rotation_hessian, rayleigh_response, response, response_group, rotation_gradient, &
    1028            2 :          energy_gradient, schur_block, coupling, schur_rhs)
    1029              :       schur(:, :) = schur_block + &
    1030              :                     MATMUL(coupling(:, 1:1), TRANSPOSE(coupling(:, 1:1)))/response_sum_a + &
    1031          280 :                     MATMUL(coupling(:, 2:2), TRANSPOSE(coupling(:, 2:2)))/response_sum_b
    1032              :       schur_error = MAXVAL(ABS(schur - rotation_hessian + &
    1033              :                                MATMUL(TRANSPOSE(rayleigh_response), &
    1034         1120 :                                       MATMUL(energy_hessian, rayleigh_response))))
    1035              : 
    1036           26 :       rotation_plus(:, :) = dense_antihermitian_exp(generator + fd_step*direction_generator)
    1037           26 :       rotation_minus(:, :) = dense_antihermitian_exp(generator - fd_step*direction_generator)
    1038            8 :       DO i = 1, nbands
    1039              :          rayleigh_a_plus(i) = REAL(DOT_PRODUCT(rotation_plus(:, i), &
    1040           96 :                                                MATMUL(hamiltonian_a, rotation_plus(:, i))), KIND=dp)
    1041              :          rayleigh_a_minus(i) = REAL(DOT_PRODUCT(rotation_minus(:, i), &
    1042          102 :                                                 MATMUL(hamiltonian_a, rotation_minus(:, i))), KIND=dp)
    1043              :          rayleigh_b_plus(i) = REAL(DOT_PRODUCT(rotation_plus(:, i), &
    1044          102 :                                                MATMUL(hamiltonian_b, rotation_plus(:, i))), KIND=dp)
    1045              :          rayleigh_b_minus(i) = REAL(DOT_PRODUCT(rotation_minus(:, i), &
    1046          104 :                                                 MATMUL(hamiltonian_b, rotation_minus(:, i))), KIND=dp)
    1047              :       END DO
    1048            8 :       energy_a_plus(:) = energy_a + fd_step*energy_direction(1:nbands)
    1049            8 :       energy_a_minus(:) = energy_a - fd_step*energy_direction(1:nbands)
    1050            8 :       energy_b_plus(:) = energy_b + fd_step*energy_direction(nbands + 1:nenergy)
    1051            8 :       energy_b_minus(:) = energy_b - fd_step*energy_direction(nbands + 1:nenergy)
    1052              :       energy_zero_a = fixed_n_mermin_value( &
    1053            2 :                       rayleigh_a, energy_a, weights, 1.65_dp, temperature, maxocc)
    1054              :       energy_zero_b = fixed_n_mermin_value( &
    1055            2 :                       rayleigh_b, energy_b, weights, 0.95_dp, temperature, maxocc)
    1056              :       energy_plus_a = fixed_n_mermin_value( &
    1057            2 :                       rayleigh_a_plus, energy_a_plus, weights, 1.65_dp, temperature, maxocc)
    1058              :       energy_plus_b = fixed_n_mermin_value( &
    1059            2 :                       rayleigh_b_plus, energy_b_plus, weights, 0.95_dp, temperature, maxocc)
    1060              :       energy_minus_a = fixed_n_mermin_value( &
    1061            2 :                        rayleigh_a_minus, energy_a_minus, weights, 1.65_dp, temperature, maxocc)
    1062              :       energy_minus_b = fixed_n_mermin_value( &
    1063            2 :                        rayleigh_b_minus, energy_b_minus, weights, 0.95_dp, temperature, maxocc)
    1064            2 :       energy_zero = energy_zero_a + energy_zero_b
    1065            2 :       energy_plus = energy_plus_a + energy_plus_b
    1066            2 :       energy_minus = energy_minus_a + energy_minus_b
    1067              :       curvature_a = DOT_PRODUCT(rotation_direction, &
    1068              :                                 MATMUL(rotation_hessian_a, rotation_direction)) - &
    1069              :                     2.0_dp*DOT_PRODUCT(energy_direction(1:nbands), &
    1070              :                                        MATMUL(energy_hessian(1:nbands, 1:nbands), &
    1071              :                                               MATMUL(rayleigh_response(1:nbands, :), &
    1072              :                                                      rotation_direction))) + &
    1073              :                     DOT_PRODUCT(energy_direction(1:nbands), &
    1074              :                                 MATMUL(energy_hessian(1:nbands, 1:nbands), &
    1075          212 :                                        energy_direction(1:nbands)))
    1076              :       curvature_b = DOT_PRODUCT(rotation_direction, &
    1077              :                                 MATMUL(rotation_hessian_b, rotation_direction)) - &
    1078              :                     2.0_dp*DOT_PRODUCT(energy_direction(nbands + 1:nenergy), &
    1079              :                                        MATMUL(energy_hessian(nbands + 1:nenergy, &
    1080              :                                                              nbands + 1:nenergy), &
    1081              :                                               MATMUL(rayleigh_response(nbands + 1:nenergy, :), &
    1082              :                                                      rotation_direction))) + &
    1083              :                     DOT_PRODUCT(energy_direction(nbands + 1:nenergy), &
    1084              :                                 MATMUL(energy_hessian(nbands + 1:nenergy, nbands + 1:nenergy), &
    1085          212 :                                        energy_direction(nbands + 1:nenergy)))
    1086            2 :       curvature = curvature_a + curvature_b
    1087              :       curvature_error = ABS((energy_plus_a - 2.0_dp*energy_zero_a + energy_minus_a)/ &
    1088            2 :                             fd_step**2 - curvature_a)
    1089              :       curvature_error = MAX( &
    1090              :                         curvature_error, &
    1091              :                         ABS((energy_plus_b - 2.0_dp*energy_zero_b + energy_minus_b)/fd_step**2 - curvature_b), &
    1092            2 :                         ABS((energy_plus - 2.0_dp*energy_zero + energy_minus)/fd_step**2 - curvature))
    1093              : 
    1094            2 :       IF (schur_error > 3.0E-11_dp .OR. curvature_error > 5.0E-6_dp) nfail = nfail + 1
    1095            2 :       IF (mynode == 0) THEN
    1096            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') "ROKS fixed-N rotation Schur errors:", &
    1097            2 :             schur_error, curvature_error
    1098              :       END IF
    1099              : 
    1100            2 :    END SUBROUTINE test_roks_fixed_n_rotation_schur
    1101              : 
    1102              : ! **************************************************************************************************
    1103              : !> \brief Check that positive and negative response modes retain their spectral magnitude.
    1104              : !> \param mynode MPI rank
    1105              : !> \param nfail accumulated number of failures
    1106              : ! **************************************************************************************************
    1107            2 :    SUBROUTINE test_symmetric_abs_solve(mynode, nfail)
    1108              :       INTEGER, INTENT(IN)                                :: mynode
    1109              :       INTEGER, INTENT(INOUT)                             :: nfail
    1110              : 
    1111              :       LOGICAL                                            :: valid
    1112              :       REAL(KIND=dp)                                      :: error
    1113              :       REAL(KIND=dp), DIMENSION(3)                        :: eigenvalues
    1114              :       REAL(KIND=dp), DIMENSION(3, 2)                     :: expected, rhs, solution
    1115              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: absolute_matrix, matrix, vectors
    1116              : 
    1117            8 :       vectors(:, 1) = [0.8_dp, 0.6_dp, 0.0_dp]
    1118            8 :       vectors(:, 2) = [-0.6_dp, 0.8_dp, 0.0_dp]
    1119            8 :       vectors(:, 3) = [0.0_dp, 0.0_dp, 1.0_dp]
    1120            2 :       eigenvalues(:) = [-0.4_dp, 1.7_dp, 0.25_dp]
    1121              :       matrix(:, :) = MATMUL(vectors, MATMUL( &
    1122              :                             RESHAPE([eigenvalues(1), 0.0_dp, 0.0_dp, 0.0_dp, eigenvalues(2), 0.0_dp, &
    1123          176 :                                      0.0_dp, 0.0_dp, eigenvalues(3)], [3, 3]), TRANSPOSE(vectors)))
    1124              :       absolute_matrix(:, :) = MATMUL(vectors, MATMUL( &
    1125              :                                      RESHAPE([ABS(eigenvalues(1)), 0.0_dp, 0.0_dp, 0.0_dp, ABS(eigenvalues(2)), 0.0_dp, &
    1126          176 :                                               0.0_dp, 0.0_dp, ABS(eigenvalues(3))], [3, 3]), TRANSPOSE(vectors)))
    1127            8 :       rhs(:, 1) = [0.3_dp, -0.5_dp, 0.7_dp]
    1128            8 :       rhs(:, 2) = [-0.2_dp, 0.4_dp, 0.1_dp]
    1129            2 :       CALL qs_ot_symmetric_abs_solve(matrix, rhs, solution, valid)
    1130           54 :       expected(:, :) = MATMUL(absolute_matrix, solution)
    1131           18 :       error = MAXVAL(ABS(expected - rhs))
    1132            2 :       IF (.NOT. valid .OR. error > 2.0E-13_dp) nfail = nfail + 1
    1133              : 
    1134            2 :       eigenvalues(:) = [-0.4_dp, 1.7_dp, 1.0E-12_dp]
    1135              :       matrix(:, :) = MATMUL(vectors, MATMUL( &
    1136              :                             RESHAPE([eigenvalues(1), 0.0_dp, 0.0_dp, 0.0_dp, eigenvalues(2), 0.0_dp, &
    1137          176 :                                      0.0_dp, 0.0_dp, eigenvalues(3)], [3, 3]), TRANSPOSE(vectors)))
    1138            8 :       rhs(:, 1) = [0.3_dp, -0.5_dp, 0.7_dp]
    1139            8 :       rhs(:, 2) = [-0.2_dp, 0.4_dp, 0.1_dp]
    1140            2 :       CALL qs_ot_symmetric_abs_solve(matrix, rhs, solution, valid)
    1141            2 :       expected(:, :) = rhs(:, :)
    1142            6 :       expected(3, :) = 0.0_dp
    1143              :       absolute_matrix(:, :) = MATMUL(vectors(:, 1:2), MATMUL( &
    1144              :                                      RESHAPE([ABS(eigenvalues(1)), 0.0_dp, 0.0_dp, ABS(eigenvalues(2))], [2, 2]), &
    1145          106 :                                      TRANSPOSE(vectors(:, 1:2))))
    1146           72 :       error = MAX(error, MAXVAL(ABS(MATMUL(absolute_matrix, solution) - expected)))
    1147            6 :       IF (.NOT. valid .OR. error > 2.0E-13_dp .OR. MAXVAL(ABS(solution(3, :))) > 2.0E-13_dp) THEN
    1148            0 :          nfail = nfail + 1
    1149              :       END IF
    1150            2 :       IF (mynode == 0) WRITE (io_unit, '(A,1X,ES13.6)') "symmetric absolute solve error:", error
    1151              : 
    1152            2 :    END SUBROUTINE test_symmetric_abs_solve
    1153              : 
    1154              : ! **************************************************************************************************
    1155              : !> \brief Check that the projected response update preserves the baseline and rejects indefiniteness.
    1156              : !> \param mynode MPI rank
    1157              : !> \param nfail accumulated number of failures
    1158              : ! **************************************************************************************************
    1159            2 :    SUBROUTINE test_projected_response_update(mynode, nfail)
    1160              :       INTEGER, INTENT(IN)                                :: mynode
    1161              :       INTEGER, INTENT(INOUT)                             :: nfail
    1162              : 
    1163              :       LOGICAL                                            :: valid
    1164              :       REAL(KIND=dp)                                      :: error, preservation_error
    1165              :       REAL(KIND=dp), DIMENSION(2)                        :: coefficients, gradient, residual
    1166              :       REAL(KIND=dp), DIMENSION(2, 2)                     :: correction, reference, total
    1167              : 
    1168            2 :       reference(:, :) = RESHAPE([4.0_dp, 0.7_dp, 0.7_dp, 2.5_dp], [2, 2])
    1169            2 :       correction(:, :) = 0.0_dp
    1170            2 :       CALL qs_ot_projected_response_update(reference, correction, coefficients, valid)
    1171            6 :       preservation_error = MAXVAL(ABS(coefficients - [1.0_dp, 0.0_dp]))
    1172            2 :       IF (.NOT. valid .OR. preservation_error > 1.0E-12_dp) nfail = nfail + 1
    1173              : 
    1174            2 :       correction(:, :) = RESHAPE([0.8_dp, -0.3_dp, -0.3_dp, 0.4_dp], [2, 2])
    1175            2 :       gradient(:) = [1.2_dp, -0.4_dp]
    1176              :       CALL qs_ot_projected_response_update( &
    1177            2 :          reference, correction, coefficients, valid, projected_gradient=gradient)
    1178           14 :       total(:, :) = reference + correction
    1179           20 :       residual(:) = MATMUL(total, coefficients) - gradient
    1180            6 :       error = MAXVAL(ABS(residual))
    1181            2 :       IF (.NOT. valid .OR. error > 1.0E-12_dp) nfail = nfail + 1
    1182              : 
    1183            2 :       correction(:, :) = RESHAPE([-8.0_dp, 0.0_dp, 0.0_dp, 0.0_dp], [2, 2])
    1184            2 :       CALL qs_ot_projected_response_update(reference, correction, coefficients, valid)
    1185            2 :       IF (valid) nfail = nfail + 1
    1186            2 :       IF (mynode == 0) WRITE (io_unit, '(A,2(1X,ES14.6))') &
    1187            1 :          "projected response preservation/residual errors:", preservation_error, error
    1188              : 
    1189            2 :    END SUBROUTINE test_projected_response_update
    1190              : 
    1191              : ! **************************************************************************************************
    1192              : !> \brief Check exact signed SR1 secants and rejection of an unresolved orthogonal residual.
    1193              : !> \param mynode MPI rank
    1194              : !> \param nfail accumulated number of failures
    1195              : ! **************************************************************************************************
    1196            2 :    SUBROUTINE test_symmetric_sr1_update(mynode, nfail)
    1197              :       INTEGER, INTENT(IN)                                :: mynode
    1198              :       INTEGER, INTENT(INOUT)                             :: nfail
    1199              : 
    1200              :       LOGICAL                                            :: valid
    1201              :       REAL(KIND=dp)                                      :: rejected_error, secant_error, &
    1202              :                                                             symmetry_error
    1203              :       REAL(KIND=dp), DIMENSION(3)                        :: orthogonal_residual, response, step
    1204              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: matrix, updated
    1205              : 
    1206            2 :       matrix(:, :) = 0.0_dp
    1207            2 :       matrix(1, 1) = 1.0_dp
    1208            2 :       matrix(2, 2) = 2.0_dp
    1209            2 :       matrix(3, 3) = 3.0_dp
    1210            2 :       step(:) = [0.3_dp, -0.4_dp, 0.2_dp]
    1211            2 :       response(:) = [0.0_dp, -0.3_dp, 0.8_dp]
    1212            2 :       CALL qs_ot_symmetric_sr1_update(matrix, step, response, updated, valid)
    1213           34 :       secant_error = MAXVAL(ABS(MATMUL(updated, step) - response))
    1214           26 :       symmetry_error = MAXVAL(ABS(updated - TRANSPOSE(updated)))
    1215            2 :       IF (.NOT. valid .OR. secant_error > 2.0E-14_dp .OR. symmetry_error > 2.0E-14_dp) THEN
    1216            0 :          nfail = nfail + 1
    1217              :       END IF
    1218              : 
    1219            2 :       orthogonal_residual(:) = [0.4_dp, 0.3_dp, 0.0_dp]
    1220           34 :       response(:) = MATMUL(matrix, step) + orthogonal_residual
    1221            2 :       CALL qs_ot_symmetric_sr1_update(matrix, step, response, updated, valid)
    1222           26 :       rejected_error = MAXVAL(ABS(updated - matrix))
    1223            2 :       IF (valid .OR. rejected_error > 2.0E-14_dp) nfail = nfail + 1
    1224            2 :       IF (mynode == 0) WRITE (io_unit, '(A,3(1X,ES13.6))') &
    1225            1 :          "symmetric SR1 secant/symmetry/rejection errors:", &
    1226            2 :          secant_error, symmetry_error, rejected_error
    1227              : 
    1228            2 :    END SUBROUTINE test_symmetric_sr1_update
    1229              : 
    1230              : ! **************************************************************************************************
    1231              : !> \brief Check the complex gauge-invariant, sign-aware density-space response secant.
    1232              : !> \param mynode MPI rank
    1233              : !> \param nfail accumulated number of failures
    1234              : ! **************************************************************************************************
    1235            2 :    SUBROUTINE test_density_secant_hessian(mynode, nfail)
    1236              :       INTEGER, INTENT(IN)                                :: mynode
    1237              :       INTEGER, INTENT(INOUT)                             :: nfail
    1238              : 
    1239              :       INTEGER, PARAMETER                                 :: n = 3, nmode = 2
    1240              :       REAL(KIND=dp), PARAMETER                           :: secant_position = 0.4_dp
    1241              : 
    1242              :       COMPLEX(KIND=dp), DIMENSION(n, n)                  :: density_step, hamiltonian_step, unitary
    1243              :       COMPLEX(KIND=dp), DIMENSION(n, n, nmode)           :: density_modes, transformed_modes
    1244              :       LOGICAL                                            :: calibrated_valid, rejected_valid, valid
    1245              :       REAL(KIND=dp) :: calibrated_error, density_norm, direct_error, gauge_error, overlap_error, &
    1246              :          rejected_error, response_work, sign_error
    1247              :       REAL(KIND=dp), DIMENSION(nmode)                    :: density_overlap, response_overlap
    1248              :       REAL(KIND=dp), DIMENSION(nmode, nmode)             :: calibrated, correction, &
    1249              :                                                             correction_gauge, correction_overlap, &
    1250              :                                                             expected, rejected
    1251              : 
    1252            2 :       density_step(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1253            2 :       density_step(1, 1) = CMPLX(0.30_dp, 0.0_dp, KIND=dp)
    1254            2 :       density_step(2, 2) = CMPLX(-0.18_dp, 0.0_dp, KIND=dp)
    1255            2 :       density_step(3, 3) = CMPLX(-0.12_dp, 0.0_dp, KIND=dp)
    1256            2 :       density_step(1, 2) = CMPLX(0.07_dp, -0.11_dp, KIND=dp)
    1257            2 :       density_step(2, 1) = CONJG(density_step(1, 2))
    1258            2 :       density_step(1, 3) = CMPLX(-0.04_dp, 0.05_dp, KIND=dp)
    1259            2 :       density_step(3, 1) = CONJG(density_step(1, 3))
    1260              : 
    1261           26 :       hamiltonian_step(:, :) = -0.45_dp*density_step
    1262            2 :       hamiltonian_step(2, 3) = CMPLX(0.06_dp, 0.03_dp, KIND=dp)
    1263            2 :       hamiltonian_step(3, 2) = CONJG(hamiltonian_step(2, 3))
    1264           26 :       density_modes(:, :, 1) = density_step
    1265           26 :       density_modes(:, :, 2) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1266            2 :       density_modes(1, 1, 2) = CMPLX(-0.2_dp, 0.0_dp, KIND=dp)
    1267            2 :       density_modes(2, 2, 2) = CMPLX(0.1_dp, 0.0_dp, KIND=dp)
    1268            2 :       density_modes(3, 3, 2) = CMPLX(0.1_dp, 0.0_dp, KIND=dp)
    1269            2 :       density_modes(1, 3, 2) = CMPLX(0.03_dp, 0.08_dp, KIND=dp)
    1270            2 :       density_modes(3, 1, 2) = CONJG(density_modes(1, 3, 2))
    1271              : 
    1272              :       CALL qs_ot_density_secant_hessian( &
    1273              :          density_step, hamiltonian_step, density_modes, correction, valid, &
    1274            2 :          density_norm, response_work)
    1275            2 :       density_overlap(1) = density_norm
    1276           26 :       density_overlap(2) = SUM(REAL(CONJG(density_step)*density_modes(:, :, 2), KIND=dp))
    1277            2 :       response_overlap(1) = response_work
    1278           26 :       response_overlap(2) = SUM(REAL(CONJG(hamiltonian_step)*density_modes(:, :, 2), KIND=dp))
    1279              :       CALL qs_ot_density_secant_projected_hessian( &
    1280            2 :          density_norm, response_work, density_overlap, response_overlap, correction_overlap, valid)
    1281              : 
    1282            2 :       expected(1, 1) = response_work
    1283            2 :       expected(1, 2) = response_overlap(2)
    1284            2 :       expected(2, 1) = response_overlap(2)
    1285              :       expected(2, 2) = (2.0_dp*density_overlap(2)*response_overlap(2) - &
    1286            2 :                         response_work*density_overlap(2)**2/density_norm)/density_norm
    1287           14 :       direct_error = MAXVAL(ABS(correction - expected))
    1288           14 :       overlap_error = MAXVAL(ABS(correction_overlap - correction))
    1289            2 :       sign_error = MAX(0.0_dp, correction(1, 1))
    1290              : 
    1291              :       CALL qs_ot_density_secant_projected_hessian( &
    1292              :          density_norm, response_work, density_overlap, response_overlap, calibrated, &
    1293            2 :          calibrated_valid, secant_mode=2, secant_position=secant_position)
    1294              :       expected(1, 1) = response_work
    1295            2 :       expected(1, 2) = response_work/secant_position
    1296            2 :       expected(2, 1) = expected(1, 2)
    1297            2 :       expected(2, 2) = response_work/secant_position**2
    1298           14 :       calibrated_error = MAXVAL(ABS(calibrated - expected))
    1299              :       CALL qs_ot_density_secant_projected_hessian( &
    1300              :          density_norm, response_work, density_overlap, response_overlap, rejected, &
    1301            2 :          rejected_valid, secant_mode=2, secant_position=0.0_dp)
    1302           14 :       rejected_error = MAXVAL(ABS(rejected))
    1303              : 
    1304            2 :       unitary(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1305            2 :       unitary(1, 1) = CMPLX(SQRT(0.5_dp), 0.0_dp, KIND=dp)
    1306            2 :       unitary(2, 1) = CMPLX(0.0_dp, SQRT(0.5_dp), KIND=dp)
    1307            2 :       unitary(1, 2) = CMPLX(0.0_dp, SQRT(0.5_dp), KIND=dp)
    1308            2 :       unitary(2, 2) = CMPLX(SQRT(0.5_dp), 0.0_dp, KIND=dp)
    1309            2 :       unitary(3, 3) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    1310          158 :       density_step = MATMUL(CONJG(TRANSPOSE(unitary)), MATMUL(density_step, unitary))
    1311          158 :       hamiltonian_step = MATMUL(CONJG(TRANSPOSE(unitary)), MATMUL(hamiltonian_step, unitary))
    1312              :       transformed_modes(:, :, 1) = &
    1313          186 :          MATMUL(CONJG(TRANSPOSE(unitary)), MATMUL(density_modes(:, :, 1), unitary))
    1314              :       transformed_modes(:, :, 2) = &
    1315          186 :          MATMUL(CONJG(TRANSPOSE(unitary)), MATMUL(density_modes(:, :, 2), unitary))
    1316              :       CALL qs_ot_density_secant_hessian( &
    1317            2 :          density_step, hamiltonian_step, transformed_modes, correction_gauge, valid)
    1318           14 :       gauge_error = MAXVAL(ABS(correction_gauge - correction))
    1319              : 
    1320              :       IF (.NOT. valid .OR. .NOT. calibrated_valid .OR. rejected_valid .OR. &
    1321              :           direct_error > 2.0E-14_dp .OR. overlap_error > 2.0E-14_dp .OR. &
    1322              :           gauge_error > 2.0E-14_dp .OR. sign_error > 2.0E-14_dp .OR. &
    1323            2 :           calibrated_error > 2.0E-14_dp .OR. rejected_error > 2.0E-14_dp) nfail = nfail + 1
    1324            2 :       IF (mynode == 0) WRITE (io_unit, '(A,6(1X,ES13.6))') &
    1325            1 :          "density secant direct/overlap/gauge/sign/calibrated/rejected errors:", &
    1326            2 :          direct_error, overlap_error, gauge_error, sign_error, calibrated_error, rejected_error
    1327              : 
    1328            2 :    END SUBROUTINE test_density_secant_hessian
    1329              : 
    1330              : ! **************************************************************************************************
    1331              : !> \brief Check that the Hxc secant is formed in the joint direct sum of both spin channels.
    1332              : !> \param mynode MPI rank
    1333              : !> \param nfail accumulated number of failures
    1334              : ! **************************************************************************************************
    1335            2 :    SUBROUTINE test_joint_spin_density_secant(mynode, nfail)
    1336              :       INTEGER, INTENT(IN)                                :: mynode
    1337              :       INTEGER, INTENT(INOUT)                             :: nfail
    1338              : 
    1339              :       INTEGER, PARAMETER                                 :: n = 2, nmode = 2, nspin = 2, &
    1340              :                                                             ntotal = n*nspin
    1341              : 
    1342              :       COMPLEX(KIND=dp), DIMENSION(n, n, nmode, nspin)    :: density_modes
    1343              :       COMPLEX(KIND=dp), DIMENSION(n, n, nspin)           :: density_step, hamiltonian_step
    1344              :       COMPLEX(KIND=dp), DIMENSION(ntotal, ntotal)        :: direct_density_step, &
    1345              :                                                             direct_hamiltonian_step
    1346              :       COMPLEX(KIND=dp), DIMENSION(ntotal, ntotal, nmode) :: direct_density_modes
    1347              :       INTEGER                                            :: first, ispin, last, mode
    1348              :       LOGICAL                                            :: direct_valid, joint_valid, spin_valid
    1349              :       REAL(KIND=dp)                                      :: coupling_size, density_norm, &
    1350              :                                                             direct_error, response_work
    1351              :       REAL(KIND=dp), DIMENSION(nmode)                    :: density_overlap, response_overlap
    1352              :       REAL(KIND=dp), DIMENSION(nmode, nmode)             :: direct_correction, joint_correction, &
    1353              :                                                             separate_correction, spin_correction
    1354              : 
    1355              :       density_step(:, :, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1356            2 :       density_step(1, 1, 1) = 0.31_dp
    1357            2 :       density_step(2, 2, 1) = -0.31_dp
    1358            2 :       density_step(1, 2, 1) = CMPLX(0.08_dp, -0.04_dp, KIND=dp)
    1359            2 :       density_step(2, 1, 1) = CONJG(density_step(1, 2, 1))
    1360            2 :       density_step(1, 1, 2) = -0.19_dp
    1361            2 :       density_step(2, 2, 2) = 0.19_dp
    1362            2 :       density_step(1, 2, 2) = CMPLX(-0.05_dp, 0.06_dp, KIND=dp)
    1363            2 :       density_step(2, 1, 2) = CONJG(density_step(1, 2, 2))
    1364              : 
    1365           14 :       hamiltonian_step(:, :, 1) = 0.67_dp*density_step(:, :, 1)
    1366            2 :       hamiltonian_step(1, 2, 1) = hamiltonian_step(1, 2, 1) + CMPLX(-0.03_dp, 0.09_dp, KIND=dp)
    1367            2 :       hamiltonian_step(2, 1, 1) = CONJG(hamiltonian_step(1, 2, 1))
    1368           14 :       hamiltonian_step(:, :, 2) = -0.43_dp*density_step(:, :, 2)
    1369            2 :       hamiltonian_step(1, 2, 2) = hamiltonian_step(1, 2, 2) + CMPLX(0.07_dp, 0.02_dp, KIND=dp)
    1370            2 :       hamiltonian_step(2, 1, 2) = CONJG(hamiltonian_step(1, 2, 2))
    1371              : 
    1372              :       density_modes(:, :, :, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1373            2 :       density_modes(1, 1, 1, 1) = 0.21_dp
    1374            2 :       density_modes(2, 2, 1, 1) = -0.21_dp
    1375            2 :       density_modes(1, 2, 1, 1) = CMPLX(0.04_dp, 0.03_dp, KIND=dp)
    1376            2 :       density_modes(2, 1, 1, 1) = CONJG(density_modes(1, 2, 1, 1))
    1377            2 :       density_modes(1, 1, 2, 1) = -0.13_dp
    1378            2 :       density_modes(2, 2, 2, 1) = 0.13_dp
    1379            2 :       density_modes(1, 2, 2, 1) = CMPLX(-0.02_dp, 0.05_dp, KIND=dp)
    1380            2 :       density_modes(2, 1, 2, 1) = CONJG(density_modes(1, 2, 2, 1))
    1381            2 :       density_modes(1, 1, 1, 2) = -0.17_dp
    1382            2 :       density_modes(2, 2, 1, 2) = 0.17_dp
    1383            2 :       density_modes(1, 2, 1, 2) = CMPLX(0.06_dp, -0.01_dp, KIND=dp)
    1384            2 :       density_modes(2, 1, 1, 2) = CONJG(density_modes(1, 2, 1, 2))
    1385            2 :       density_modes(1, 1, 2, 2) = 0.09_dp
    1386            2 :       density_modes(2, 2, 2, 2) = -0.09_dp
    1387            2 :       density_modes(1, 2, 2, 2) = CMPLX(0.03_dp, 0.04_dp, KIND=dp)
    1388            2 :       density_modes(2, 1, 2, 2) = CONJG(density_modes(1, 2, 2, 2))
    1389              : 
    1390            2 :       direct_density_step(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1391            2 :       direct_hamiltonian_step(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1392            2 :       direct_density_modes(:, :, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1393            2 :       density_norm = 0.0_dp
    1394            2 :       response_work = 0.0_dp
    1395            2 :       density_overlap(:) = 0.0_dp
    1396            2 :       response_overlap(:) = 0.0_dp
    1397            2 :       separate_correction(:, :) = 0.0_dp
    1398            2 :       spin_valid = .TRUE.
    1399            6 :       DO ispin = 1, nspin
    1400            4 :          first = (ispin - 1)*n + 1
    1401            4 :          last = ispin*n
    1402           28 :          direct_density_step(first:last, first:last) = density_step(:, :, ispin)
    1403           28 :          direct_hamiltonian_step(first:last, first:last) = hamiltonian_step(:, :, ispin)
    1404           60 :          direct_density_modes(first:last, first:last, :) = density_modes(:, :, :, ispin)
    1405              :          density_norm = density_norm + &
    1406              :                         SUM(REAL(CONJG(density_step(:, :, ispin))* &
    1407           28 :                                  density_step(:, :, ispin), KIND=dp))
    1408              :          response_work = response_work + &
    1409              :                          SUM(REAL(CONJG(density_step(:, :, ispin))* &
    1410           28 :                                   hamiltonian_step(:, :, ispin), KIND=dp))
    1411           12 :          DO mode = 1, nmode
    1412              :             density_overlap(mode) = density_overlap(mode) + &
    1413              :                                     SUM(REAL(CONJG(density_step(:, :, ispin))* &
    1414           56 :                                              density_modes(:, :, mode, ispin), KIND=dp))
    1415              :             response_overlap(mode) = response_overlap(mode) + &
    1416              :                                      SUM(REAL(CONJG(hamiltonian_step(:, :, ispin))* &
    1417           60 :                                               density_modes(:, :, mode, ispin), KIND=dp))
    1418              :          END DO
    1419              :          CALL qs_ot_density_secant_hessian( &
    1420              :             density_step(:, :, ispin), hamiltonian_step(:, :, ispin), &
    1421            4 :             density_modes(:, :, :, ispin), spin_correction, direct_valid)
    1422            4 :          spin_valid = spin_valid .AND. direct_valid
    1423           30 :          separate_correction = separate_correction + spin_correction
    1424              :       END DO
    1425              : 
    1426              :       CALL qs_ot_density_secant_hessian( &
    1427              :          direct_density_step, direct_hamiltonian_step, direct_density_modes, &
    1428            2 :          direct_correction, direct_valid)
    1429              :       CALL qs_ot_density_secant_projected_hessian( &
    1430              :          density_norm, response_work, density_overlap, response_overlap, &
    1431            2 :          joint_correction, joint_valid)
    1432           14 :       direct_error = MAXVAL(ABS(joint_correction - direct_correction))
    1433           14 :       coupling_size = MAXVAL(ABS(joint_correction - separate_correction))
    1434              : 
    1435              :       IF (.NOT. direct_valid .OR. .NOT. joint_valid .OR. .NOT. spin_valid .OR. &
    1436            2 :           direct_error > 3.0E-14_dp .OR. coupling_size < 1.0E-5_dp) nfail = nfail + 1
    1437            2 :       IF (mynode == 0) WRITE (io_unit, '(A,2(1X,ES13.6))') &
    1438            1 :          "joint-spin density secant direct error/coupling:", direct_error, coupling_size
    1439              : 
    1440            2 :    END SUBROUTINE test_joint_spin_density_secant
    1441              : 
    1442              : ! **************************************************************************************************
    1443              : !> \brief Check the coupled finite-chart density tangent against a central finite difference.
    1444              : !> \param mynode MPI rank
    1445              : !> \param nfail accumulated number of failures
    1446              : ! **************************************************************************************************
    1447            2 :    SUBROUTINE test_density_tangent(mynode, nfail)
    1448              :       INTEGER, INTENT(IN)                                :: mynode
    1449              :       INTEGER, INTENT(INOUT)                             :: nfail
    1450              : 
    1451              :       INTEGER, PARAMETER                                 :: nbands = 3, nrotation = 6
    1452              :       REAL(KIND=dp), PARAMETER                           :: fd_step = 2.0E-6_dp, &
    1453              :                                                             kpoint_weight = 0.625_dp
    1454              : 
    1455              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands) :: density_minus, density_plus, density_tangent, &
    1456              :          direction_generator, generator, rotation, rotation_minus, rotation_plus, weighted_rotation
    1457              :       INTEGER                                            :: i, j, r
    1458              :       REAL(KIND=dp)                                      :: error, trace_error
    1459              :       REAL(KIND=dp), DIMENSION(nbands)                   :: occupation, weighted_occupation_step
    1460              :       REAL(KIND=dp), DIMENSION(nrotation)                :: rotation_step
    1461              : 
    1462            2 :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1463            2 :       generator(1, 2) = CMPLX(0.17_dp, -0.09_dp, KIND=dp)
    1464            2 :       generator(2, 1) = -CONJG(generator(1, 2))
    1465            2 :       generator(1, 3) = CMPLX(-0.06_dp, 0.11_dp, KIND=dp)
    1466            2 :       generator(3, 1) = -CONJG(generator(1, 3))
    1467            2 :       generator(2, 3) = CMPLX(0.08_dp, 0.04_dp, KIND=dp)
    1468            2 :       generator(3, 2) = -CONJG(generator(2, 3))
    1469            2 :       occupation(:) = [1.72_dp, 0.83_dp, 0.31_dp]
    1470            2 :       rotation_step(:) = [0.21_dp, -0.14_dp, -0.08_dp, 0.17_dp, 0.11_dp, 0.06_dp]
    1471            2 :       weighted_occupation_step(:) = [-0.037_dp, 0.052_dp, -0.015_dp]
    1472              : 
    1473              :       CALL qs_ot_density_tangent( &
    1474              :          generator, occupation, kpoint_weight, rotation_step, weighted_occupation_step, &
    1475            2 :          density_tangent, difference_step=fd_step)
    1476              : 
    1477            2 :       direction_generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1478            2 :       r = 0
    1479            6 :       DO i = 1, nbands - 1
    1480           12 :          DO j = i + 1, nbands
    1481            6 :             r = r + 1
    1482            6 :             direction_generator(i, j) = CMPLX(rotation_step(r), 0.0_dp, KIND=dp)
    1483            6 :             direction_generator(j, i) = -direction_generator(i, j)
    1484            6 :             r = r + 1
    1485              :             direction_generator(i, j) = direction_generator(i, j) + &
    1486            6 :                                         CMPLX(0.0_dp, rotation_step(r), KIND=dp)
    1487              :             direction_generator(j, i) = direction_generator(j, i) + &
    1488           10 :                                         CMPLX(0.0_dp, rotation_step(r), KIND=dp)
    1489              :          END DO
    1490              :       END DO
    1491            2 :       rotation = dense_antihermitian_exp(generator)
    1492           26 :       rotation_plus = dense_antihermitian_exp(generator + fd_step*direction_generator)
    1493           26 :       rotation_minus = dense_antihermitian_exp(generator - fd_step*direction_generator)
    1494            2 :       weighted_rotation(:, :) = rotation_plus
    1495            8 :       DO j = 1, nbands
    1496              :          weighted_rotation(:, j) = &
    1497              :             (kpoint_weight*occupation(j) + fd_step*weighted_occupation_step(j))* &
    1498           26 :             weighted_rotation(:, j)
    1499              :       END DO
    1500           80 :       density_plus(:, :) = MATMUL(weighted_rotation, CONJG(TRANSPOSE(rotation_plus)))
    1501            2 :       weighted_rotation(:, :) = rotation_minus
    1502            8 :       DO j = 1, nbands
    1503              :          weighted_rotation(:, j) = &
    1504              :             (kpoint_weight*occupation(j) - fd_step*weighted_occupation_step(j))* &
    1505           26 :             weighted_rotation(:, j)
    1506              :       END DO
    1507           80 :       density_minus(:, :) = MATMUL(weighted_rotation, CONJG(TRANSPOSE(rotation_minus)))
    1508              :       density_plus(:, :) = MATMUL(CONJG(TRANSPOSE(rotation)), &
    1509          182 :                                   MATMUL((density_plus - density_minus)/(2.0_dp*fd_step), rotation))
    1510           26 :       error = MAXVAL(ABS(density_tangent - density_plus))
    1511              :       trace_error = ABS(REAL(SUM([(density_tangent(i, i), i=1, nbands)]), KIND=dp) - &
    1512            8 :                         SUM(weighted_occupation_step))
    1513              : 
    1514            2 :       IF (error > 3.0E-9_dp .OR. trace_error > 3.0E-10_dp) nfail = nfail + 1
    1515            2 :       IF (mynode == 0) WRITE (io_unit, '(A,2(1X,ES13.6))') &
    1516            1 :          "finite complex density tangent errors:", error, trace_error
    1517              : 
    1518            2 :    END SUBROUTINE test_density_tangent
    1519              : 
    1520              : ! **************************************************************************************************
    1521              : !> \brief Check density-secants when accepted endpoint orbital subspaces differ.
    1522              : !> \param mynode MPI rank
    1523              : !> \param nfail accumulated number of failures
    1524              : ! **************************************************************************************************
    1525            2 :    SUBROUTINE test_density_secant_moving_subspace(mynode, nfail)
    1526              :       INTEGER, INTENT(IN)                                :: mynode
    1527              :       INTEGER, INTENT(INOUT)                             :: nfail
    1528              : 
    1529              :       INTEGER, PARAMETER                                 :: nao = 4, nbands = 3, nmode = 2
    1530              :       REAL(KIND=dp), PARAMETER                           :: weight = 0.625_dp
    1531              : 
    1532              :       COMPLEX(KIND=dp), DIMENSION(nao, nao)              :: density_step, hamiltonian_step
    1533              :       COMPLEX(KIND=dp), DIMENSION(nao, nao, nmode)       :: density_modes_ao
    1534              :       COMPLEX(KIND=dp), DIMENSION(nao, nbands)           :: c0, c1
    1535              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands)        :: h0, h1, overlap
    1536              :       COMPLEX(KIND=dp), DIMENSION(nbands, nbands, nmode) :: density_modes
    1537              :       INTEGER                                            :: i
    1538              :       LOGICAL                                            :: valid
    1539              :       REAL(KIND=dp)                                      :: density_norm, density_norm_ref, error, &
    1540              :                                                             response_work, response_work_ref
    1541              :       REAL(KIND=dp), DIMENSION(nbands)                   :: occupation0, occupation1
    1542              :       REAL(KIND=dp), DIMENSION(nmode)                    :: density_overlap, density_overlap_ref, &
    1543              :                                                             response_overlap, response_overlap_ref
    1544              : 
    1545            2 :       c0(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1546            2 :       c0(1, 1) = 1.0_dp
    1547            2 :       c0(2, 2) = 1.0_dp
    1548            2 :       c0(3, 3) = 1.0_dp
    1549            2 :       c1(:, :) = c0
    1550            2 :       c1(1, 1) = COS(0.31_dp)
    1551            2 :       c1(4, 1) = SIN(0.31_dp)
    1552            2 :       c1(2, 2) = COS(0.23_dp)
    1553            2 :       c1(3, 2) = CMPLX(0.0_dp, SIN(0.23_dp), KIND=dp)
    1554            2 :       c1(2, 3) = CMPLX(0.0_dp, SIN(0.23_dp), KIND=dp)
    1555            2 :       c1(3, 3) = COS(0.23_dp)
    1556            2 :       occupation0(:) = [1.73_dp, 0.82_dp, 0.82_dp]
    1557            2 :       occupation1(:) = [1.61_dp, 0.91_dp, 0.76_dp]
    1558              : 
    1559            2 :       hamiltonian_step(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1560            2 :       hamiltonian_step(1, 1) = 0.21_dp
    1561            2 :       hamiltonian_step(2, 2) = -0.17_dp
    1562            2 :       hamiltonian_step(3, 3) = 0.09_dp
    1563            2 :       hamiltonian_step(4, 4) = -0.04_dp
    1564            2 :       hamiltonian_step(1, 4) = CMPLX(0.07_dp, -0.03_dp, KIND=dp)
    1565            2 :       hamiltonian_step(4, 1) = CONJG(hamiltonian_step(1, 4))
    1566            2 :       hamiltonian_step(2, 3) = CMPLX(-0.05_dp, 0.08_dp, KIND=dp)
    1567            2 :       hamiltonian_step(3, 2) = CONJG(hamiltonian_step(2, 3))
    1568            2 :       density_modes(:, :, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1569            2 :       density_modes(1, 1, 1) = 0.13_dp
    1570            2 :       density_modes(2, 2, 1) = -0.08_dp
    1571            2 :       density_modes(3, 3, 1) = -0.05_dp
    1572            2 :       density_modes(1, 2, 2) = CMPLX(0.04_dp, -0.06_dp, KIND=dp)
    1573            2 :       density_modes(2, 1, 2) = CONJG(density_modes(1, 2, 2))
    1574              : 
    1575           98 :       overlap(:, :) = MATMUL(CONJG(TRANSPOSE(c0)), c1)
    1576              :       ! Keep the reference path independent of optimized complex MATMUL expressions.
    1577            2 :       CALL reference_project_operator(c0, hamiltonian_step, h0)
    1578            2 :       CALL reference_project_operator(c1, hamiltonian_step, h1)
    1579              :       CALL qs_ot_density_secant_orbital_overlaps( &
    1580              :          overlap, occupation0, occupation1, h0, h1, density_modes, weight, density_norm, &
    1581            2 :          response_work, density_overlap, response_overlap, valid)
    1582              : 
    1583            2 :       density_step(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1584            8 :       DO i = 1, nbands
    1585              :          density_step = density_step + weight*occupation1(i)* &
    1586              :                         SPREAD(c1(:, i), DIM=2, NCOPIES=nao)* &
    1587              :                         SPREAD(CONJG(c1(:, i)), DIM=1, NCOPIES=nao) - &
    1588              :                         weight*occupation0(i)* &
    1589              :                         SPREAD(c0(:, i), DIM=2, NCOPIES=nao)* &
    1590          176 :                         SPREAD(CONJG(c0(:, i)), DIM=1, NCOPIES=nao)
    1591              :       END DO
    1592            6 :       DO i = 1, nmode
    1593            4 :          CALL reference_transform_density_mode(c1, density_modes(:, :, i), density_modes_ao(:, :, i))
    1594              :          density_overlap_ref(i) = &
    1595            4 :             reference_frobenius_product_real(density_step, density_modes_ao(:, :, i))/weight
    1596              :          response_overlap_ref(i) = &
    1597            6 :             reference_frobenius_product_real(hamiltonian_step, density_modes_ao(:, :, i))
    1598              :       END DO
    1599            2 :       density_norm_ref = reference_frobenius_product_real(density_step, density_step)/weight
    1600            2 :       response_work_ref = reference_frobenius_product_real(density_step, hamiltonian_step)
    1601            2 :       error = MAX(ABS(density_norm - density_norm_ref), ABS(response_work - response_work_ref))
    1602            6 :       error = MAX(error, MAXVAL(ABS(density_overlap - density_overlap_ref)))
    1603            6 :       error = MAX(error, MAXVAL(ABS(response_overlap - response_overlap_ref)))
    1604              : 
    1605            2 :       IF (.NOT. valid .OR. error > 3.0E-13_dp) nfail = nfail + 1
    1606            2 :       IF (mynode == 0) WRITE (io_unit, '(A,1X,ES13.6)') &
    1607            1 :          "moving-subspace density secant error:", error
    1608              : 
    1609            2 :    END SUBROUTINE test_density_secant_moving_subspace
    1610              : 
    1611              : ! **************************************************************************************************
    1612              : !> \brief Reference projection C^H H C using scalar operations.
    1613              : !> \param orbitals orbital coefficient matrix C
    1614              : !> \param hamiltonian operator matrix H
    1615              : !> \param projected projected operator
    1616              : ! **************************************************************************************************
    1617            4 :    SUBROUTINE reference_project_operator(orbitals, hamiltonian, projected)
    1618              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: orbitals, hamiltonian
    1619              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: projected
    1620              : 
    1621              :       COMPLEX(KIND=dp)                                   :: value
    1622              :       INTEGER                                            :: i, j, mu, nu
    1623              : 
    1624           16 :       DO j = 1, SIZE(orbitals, 2)
    1625           52 :          DO i = 1, SIZE(orbitals, 2)
    1626           36 :             value = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1627          180 :             DO nu = 1, SIZE(orbitals, 1)
    1628          756 :                DO mu = 1, SIZE(orbitals, 1)
    1629          720 :                   value = value + CONJG(orbitals(mu, i))*hamiltonian(mu, nu)*orbitals(nu, j)
    1630              :                END DO
    1631              :             END DO
    1632           48 :             projected(i, j) = value
    1633              :          END DO
    1634              :       END DO
    1635              : 
    1636            4 :    END SUBROUTINE reference_project_operator
    1637              : 
    1638              : ! **************************************************************************************************
    1639              : !> \brief Reference transformation C M C^H using scalar operations.
    1640              : !> \param orbitals orbital coefficient matrix C
    1641              : !> \param mode density mode M
    1642              : !> \param transformed transformed density mode
    1643              : ! **************************************************************************************************
    1644            4 :    SUBROUTINE reference_transform_density_mode(orbitals, mode, transformed)
    1645              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: orbitals, mode
    1646              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: transformed
    1647              : 
    1648              :       COMPLEX(KIND=dp)                                   :: value
    1649              :       INTEGER                                            :: i, j, m, n
    1650              : 
    1651           20 :       DO j = 1, SIZE(orbitals, 1)
    1652           84 :          DO i = 1, SIZE(orbitals, 1)
    1653           64 :             value = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1654          256 :             DO n = 1, SIZE(orbitals, 2)
    1655          832 :                DO m = 1, SIZE(orbitals, 2)
    1656          768 :                   value = value + orbitals(i, m)*mode(m, n)*CONJG(orbitals(j, n))
    1657              :                END DO
    1658              :             END DO
    1659           80 :             transformed(i, j) = value
    1660              :          END DO
    1661              :       END DO
    1662              : 
    1663            4 :    END SUBROUTINE reference_transform_density_mode
    1664              : 
    1665              : ! **************************************************************************************************
    1666              : !> \brief Real part of the complex Frobenius inner product.
    1667              : !> \param matrix_a first matrix
    1668              : !> \param matrix_b second matrix
    1669              : !> \return real Frobenius product
    1670              : ! **************************************************************************************************
    1671           12 :    FUNCTION reference_frobenius_product_real(matrix_a, matrix_b) RESULT(value)
    1672              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: matrix_a, matrix_b
    1673              :       REAL(KIND=dp)                                      :: value
    1674              : 
    1675              :       value = SUM(REAL(matrix_a, KIND=dp)*REAL(matrix_b, KIND=dp) + &
    1676          252 :                   AIMAG(matrix_a)*AIMAG(matrix_b))
    1677              : 
    1678           12 :    END FUNCTION reference_frobenius_product_real
    1679              : 
    1680              : ! **************************************************************************************************
    1681              : !> \brief fixed-occupation band energy for a dense finite rotation
    1682              : !> \param rotation unitary REF rotation
    1683              : !> \param hamiltonian fixed Hermitian Hamiltonian in the REF basis
    1684              : !> \param occupation band occupations
    1685              : !> \param kpoint_weight irreducible-k-point weight
    1686              : !> \return weighted band energy
    1687              : ! **************************************************************************************************
    1688            6 :    FUNCTION finite_rotation_energy(rotation, hamiltonian, occupation, kpoint_weight) RESULT(energy)
    1689              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: rotation, hamiltonian
    1690              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation
    1691              :       REAL(KIND=dp), INTENT(IN)                          :: kpoint_weight
    1692              :       REAL(KIND=dp)                                      :: energy
    1693              : 
    1694              :       INTEGER                                            :: i
    1695              : 
    1696            6 :       energy = 0.0_dp
    1697           24 :       DO i = 1, SIZE(occupation)
    1698              :          energy = energy + kpoint_weight*occupation(i)* &
    1699              :                   REAL(DOT_PRODUCT(rotation(:, i), &
    1700          330 :                                    MATMUL(hamiltonian, rotation(:, i))), KIND=dp)
    1701              :       END DO
    1702              : 
    1703            6 :    END FUNCTION finite_rotation_energy
    1704              : 
    1705              : ! **************************************************************************************************
    1706              : !> \brief Solve fixed-N Fermi occupations by bisection.
    1707              : !> \param energies band energies
    1708              : !> \param weights integration weights
    1709              : !> \param target_electrons requested electron number
    1710              : !> \param temperature electronic temperature in energy units
    1711              : !> \param maxocc maximum band occupation
    1712              : !> \param occupation resulting occupations
    1713              : !> \param mu resulting chemical potential
    1714              : ! **************************************************************************************************
    1715           80 :    SUBROUTINE fixed_n_fermi_occupations(energies, weights, target_electrons, temperature, &
    1716           40 :                                         maxocc, occupation, mu)
    1717              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: energies, weights
    1718              :       REAL(KIND=dp), INTENT(IN)                          :: target_electrons, temperature, maxocc
    1719              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: occupation
    1720              :       REAL(KIND=dp), INTENT(OUT)                         :: mu
    1721              : 
    1722              :       INTEGER                                            :: i, iteration
    1723              :       REAL(KIND=dp)                                      :: high, low, number
    1724              : 
    1725          170 :       low = MINVAL(energies) - 80.0_dp*temperature
    1726          170 :       high = MAXVAL(energies) + 80.0_dp*temperature
    1727         6440 :       DO iteration = 1, 160
    1728         6400 :          mu = 0.5_dp*(low + high)
    1729         6400 :          number = 0.0_dp
    1730        27200 :          DO i = 1, SIZE(energies)
    1731        20800 :             occupation(i) = fermi_occupation(energies(i), mu, temperature, maxocc)
    1732        27200 :             number = number + weights(i)*occupation(i)
    1733              :          END DO
    1734         6440 :          IF (number < target_electrons) THEN
    1735              :             low = mu
    1736              :          ELSE
    1737         3530 :             high = mu
    1738              :          END IF
    1739              :       END DO
    1740           40 :       mu = 0.5_dp*(low + high)
    1741          170 :       DO i = 1, SIZE(energies)
    1742          170 :          occupation(i) = fermi_occupation(energies(i), mu, temperature, maxocc)
    1743              :       END DO
    1744              : 
    1745           40 :    END SUBROUTINE fixed_n_fermi_occupations
    1746              : 
    1747              : ! **************************************************************************************************
    1748              : !> \brief Numerically stable Fermi occupation.
    1749              : !> \param energy band energy
    1750              : !> \param mu chemical potential
    1751              : !> \param temperature electronic temperature in energy units
    1752              : !> \param maxocc maximum band occupation
    1753              : !> \return Fermi occupation
    1754              : ! **************************************************************************************************
    1755        20930 :    PURE FUNCTION fermi_occupation(energy, mu, temperature, maxocc) RESULT(occupation)
    1756              :       REAL(KIND=dp), INTENT(IN)                          :: energy, mu, temperature, maxocc
    1757              :       REAL(KIND=dp)                                      :: occupation
    1758              : 
    1759              :       REAL(KIND=dp)                                      :: x
    1760              : 
    1761        20930 :       x = (energy - mu)/temperature
    1762        20930 :       IF (x > 40.0_dp) THEN
    1763           44 :          occupation = maxocc*EXP(-x)
    1764        20886 :       ELSE IF (x < -40.0_dp) THEN
    1765           46 :          occupation = maxocc*(1.0_dp - EXP(x))
    1766              :       ELSE
    1767        20840 :          occupation = maxocc/(1.0_dp + EXP(x))
    1768              :       END IF
    1769              : 
    1770        20930 :    END FUNCTION fermi_occupation
    1771              : 
    1772              : ! **************************************************************************************************
    1773              : !> \brief Fixed-H fixed-N Mermin free energy used by the finite-difference test.
    1774              : !> \param rayleigh Hamiltonian expectation values
    1775              : !> \param energies auxiliary band energies
    1776              : !> \param weights integration weights
    1777              : !> \param target_electrons requested electron number
    1778              : !> \param temperature electronic temperature in energy units
    1779              : !> \param maxocc maximum band occupation
    1780              : !> \return fixed-N Mermin free energy
    1781              : ! **************************************************************************************************
    1782           22 :    FUNCTION fixed_n_mermin_value(rayleigh, energies, weights, target_electrons, &
    1783              :                                  temperature, maxocc) RESULT(value)
    1784              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: rayleigh, energies, weights
    1785              :       REAL(KIND=dp), INTENT(IN)                          :: target_electrons, temperature, maxocc
    1786              :       REAL(KIND=dp)                                      :: value
    1787              : 
    1788              :       INTEGER                                            :: i
    1789              :       REAL(KIND=dp)                                      :: mu, p
    1790           44 :       REAL(KIND=dp), DIMENSION(SIZE(energies))           :: occupation
    1791              : 
    1792              :       CALL fixed_n_fermi_occupations(energies, weights, target_electrons, temperature, &
    1793           22 :                                      maxocc, occupation, mu)
    1794           22 :       value = 0.0_dp
    1795           92 :       DO i = 1, SIZE(energies)
    1796           70 :          p = MAX(EPSILON(1.0_dp), MIN(1.0_dp - EPSILON(1.0_dp), occupation(i)/maxocc))
    1797              :          value = value + weights(i)*(occupation(i)*rayleigh(i) + &
    1798           92 :                                      temperature*maxocc*(p*LOG(p) + (1.0_dp - p)*LOG(1.0_dp - p)))
    1799              :       END DO
    1800              : 
    1801           22 :    END FUNCTION fixed_n_mermin_value
    1802              : 
    1803              : ! **************************************************************************************************
    1804              : !> \brief Canonical weighted occupation projector used by the finite-difference test.
    1805              : !> \param hamiltonian Hermitian Hamiltonian
    1806              : !> \param weights integration weights
    1807              : !> \param target_electrons requested electron number
    1808              : !> \param temperature electronic temperature in energy units
    1809              : !> \param maxocc maximum band occupation
    1810              : !> \param projector weighted spectral occupation projector
    1811              : ! **************************************************************************************************
    1812            4 :    SUBROUTINE fixed_n_spectral_projector(hamiltonian, weights, target_electrons, temperature, &
    1813            4 :                                          maxocc, projector)
    1814              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: hamiltonian
    1815              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: weights
    1816              :       REAL(KIND=dp), INTENT(IN)                          :: target_electrons, temperature, maxocc
    1817              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: projector
    1818              : 
    1819              :       COMPLEX(KIND=dp), DIMENSION(SIZE(hamiltonian, 1), &
    1820            8 :          SIZE(hamiltonian, 1))                           :: vectors, weighted_vectors
    1821              :       INTEGER                                            :: i
    1822              :       REAL(KIND=dp)                                      :: mu
    1823            8 :       REAL(KIND=dp), DIMENSION(SIZE(hamiltonian, 1))     :: eigenvalues, occupation
    1824              : 
    1825            4 :       CALL diag_complex(hamiltonian, vectors, eigenvalues)
    1826              :       CALL fixed_n_fermi_occupations(eigenvalues, weights, target_electrons, temperature, &
    1827            4 :                                      maxocc, occupation, mu)
    1828           52 :       weighted_vectors(:, :) = vectors(:, :)
    1829           16 :       DO i = 1, SIZE(eigenvalues)
    1830           52 :          weighted_vectors(:, i) = weights(i)*occupation(i)*weighted_vectors(:, i)
    1831              :       END DO
    1832          216 :       projector(:, :) = MATMUL(weighted_vectors, CONJG(TRANSPOSE(vectors)))
    1833              : 
    1834            4 :    END SUBROUTINE fixed_n_spectral_projector
    1835              : 
    1836              : ! **************************************************************************************************
    1837              : !> \brief Check the real finite rotation pullback used by Gamma-point REF OT.
    1838              : !> \param para_env parallel environment
    1839              : !> \param nfail accumulated number of failures
    1840              : ! **************************************************************************************************
    1841            2 :    SUBROUTINE test_real_rotation_frechet(para_env, nfail)
    1842              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    1843              :       INTEGER, INTENT(INOUT)                             :: nfail
    1844              : 
    1845              :       INTEGER, PARAMETER                                 :: m = 3
    1846              : 
    1847              :       INTEGER                                            :: i
    1848            4 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist, col_size, row_dist, row_size
    1849            2 :       INTEGER, DIMENSION(:, :), POINTER                  :: pgrid
    1850              :       LOGICAL                                            :: found
    1851              :       REAL(KIND=dp)                                      :: error, fd_slope, hstep, predicted
    1852            2 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block
    1853              :       REAL(KIND=dp), DIMENSION(m), TARGET                :: rotation_evals
    1854              :       REAL(KIND=dp), DIMENSION(m, m)                     :: dedu, direction, generator, gradient, &
    1855              :                                                             rotation_minus, rotation_plus
    1856              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    1857              :       TYPE(dbcsr_distribution_type)                      :: dist
    1858           16 :       TYPE(dbcsr_type), DIMENSION(7), TARGET             :: matrices
    1859              :       TYPE(mp_para_env_type), POINTER                    :: local_para_env
    1860          324 :       TYPE(qs_ot_type)                                   :: rotation_env
    1861              : 
    1862            2 :       NULLIFY (blacs_env, local_para_env)
    1863            2 :       generator(:, :) = 0.0_dp
    1864            2 :       generator(1, 2) = 0.23_dp
    1865            2 :       generator(2, 1) = -generator(1, 2)
    1866            2 :       generator(1, 3) = -0.11_dp
    1867            2 :       generator(3, 1) = -generator(1, 3)
    1868            2 :       generator(2, 3) = 0.17_dp
    1869            2 :       generator(3, 2) = -generator(2, 3)
    1870              : 
    1871            2 :       direction(:, :) = 0.0_dp
    1872            2 :       direction(1, 2) = -0.09_dp
    1873            2 :       direction(2, 1) = -direction(1, 2)
    1874            2 :       direction(1, 3) = 0.14_dp
    1875            2 :       direction(3, 1) = -direction(1, 3)
    1876            2 :       direction(2, 3) = 0.06_dp
    1877            2 :       direction(3, 2) = -direction(2, 3)
    1878              : 
    1879              :       dedu = RESHAPE([0.31_dp, -0.27_dp, 0.08_dp, &
    1880              :                       0.19_dp, 0.42_dp, -0.16_dp, &
    1881            2 :                       -0.07_dp, 0.21_dp, 0.35_dp], [m, m])
    1882              : 
    1883            0 :       ALLOCATE (local_para_env)
    1884            2 :       CALL local_para_env%from_dup(mp_comm_self)
    1885            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=local_para_env)
    1886            2 :       ALLOCATE (pgrid(0:0, 0:0), row_dist(1), col_dist(1), row_size(1), col_size(1))
    1887            6 :       pgrid(:, :) = 0
    1888            4 :       row_dist(:) = 0
    1889            4 :       col_dist(:) = 0
    1890            4 :       row_size(:) = m
    1891            4 :       col_size(:) = m
    1892              :       CALL dbcsr_distribution_new(dist, group=mp_comm_self%get_handle(), pgrid=pgrid, &
    1893            2 :                                   row_dist=row_dist, col_dist=col_dist)
    1894           16 :       DO i = 1, SIZE(matrices)
    1895           16 :          CALL create_one_block(matrices(i), 'real_rotation', dist, row_size, col_size)
    1896              :       END DO
    1897            2 :       CALL dbcsr_put_block(matrices(2), 1, 1, generator)
    1898            2 :       CALL dbcsr_put_block(matrices(3), 1, 1, dedu)
    1899              : 
    1900            2 :       rotation_env%para_env => local_para_env
    1901            2 :       rotation_env%blacs_env => blacs_env
    1902            2 :       rotation_env%rot_mat_u => matrices(1)
    1903            2 :       rotation_env%rot_mat_x => matrices(2)
    1904            2 :       rotation_env%rot_mat_dedu => matrices(3)
    1905            2 :       rotation_env%rot_mat_gx => matrices(4)
    1906            2 :       rotation_env%rot_mat_evec_re => matrices(5)
    1907            2 :       rotation_env%rot_mat_evec_im => matrices(6)
    1908            2 :       rotation_env%matrix_buf1 => matrices(7)
    1909            2 :       rotation_env%rot_mat_evals => rotation_evals
    1910              : 
    1911            2 :       CALL qs_ot_generate_rotation(rotation_env)
    1912            2 :       CALL qs_ot_rot_mat_derivative(rotation_env)
    1913            2 :       gradient(:, :) = 0.0_dp
    1914            2 :       CALL dbcsr_get_block_p(matrices(4), 1, 1, block, found)
    1915           26 :       IF (found) gradient = block
    1916              : 
    1917            2 :       hstep = 1.0E-6_dp
    1918              :       rotation_plus = REAL(dense_antihermitian_exp( &
    1919           50 :                            CMPLX(generator + hstep*direction, 0.0_dp, KIND=dp)), KIND=dp)
    1920              :       rotation_minus = REAL(dense_antihermitian_exp( &
    1921           50 :                             CMPLX(generator - hstep*direction, 0.0_dp, KIND=dp)), KIND=dp)
    1922           26 :       fd_slope = SUM(dedu*(rotation_plus - rotation_minus))/(2.0_dp*hstep)
    1923           26 :       predicted = 0.5_dp*SUM(gradient*direction)
    1924            2 :       error = ABS(predicted - fd_slope)
    1925            2 :       IF (error > 5.0E-8_dp) nfail = nfail + 1
    1926            2 :       IF (para_env%is_source()) THEN
    1927            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'real finite rotation slope fd/predicted/error', &
    1928            2 :             fd_slope, predicted, error
    1929              :       END IF
    1930              : 
    1931           16 :       DO i = 1, SIZE(matrices)
    1932           16 :          CALL dbcsr_release(matrices(i))
    1933              :       END DO
    1934            2 :       CALL dbcsr_distribution_release(dist)
    1935            2 :       DEALLOCATE (pgrid, row_dist, col_dist, row_size, col_size)
    1936            2 :       CALL cp_blacs_env_release(blacs_env)
    1937            2 :       CALL mp_para_env_release(local_para_env)
    1938              : 
    1939           12 :    END SUBROUTINE test_real_rotation_frechet
    1940              : 
    1941              : ! **************************************************************************************************
    1942              : !> \brief Check the combined finite complex REF and rotation derivatives.
    1943              : !> \param para_env parallel environment
    1944              : !> \param nfail accumulated number of failures
    1945              : ! **************************************************************************************************
    1946            2 :    SUBROUTINE test_complex_ref_rotation(para_env, nfail)
    1947              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    1948              :       INTEGER, INTENT(INOUT)                             :: nfail
    1949              : 
    1950              :       COMPLEX(KIND=dp), DIMENSION(k, k)                  :: direction_a, generator, gradient_a, &
    1951              :                                                             inverse_sqrt, rotation
    1952              :       COMPLEX(KIND=dp), DIMENSION(n, k)                  :: c_dense, c_reference, direction_x, &
    1953              :                                                             gradient_x, hc_dense, q_dense, x_dense
    1954              :       COMPLEX(KIND=dp), DIMENSION(n, n)                  :: hamiltonian, overlap
    1955              :       INTEGER                                            :: i, j
    1956            4 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist, col_k, col_n, row_dist, row_k, &
    1957            2 :                                                             row_n
    1958            2 :       INTEGER, DIMENSION(:, :), POINTER                  :: pgrid
    1959              :       REAL(KIND=dp)                                      :: error_c, error_ref, error_rot, fd_ref, &
    1960              :                                                             fd_rot, hstep, predicted_ref, &
    1961              :                                                             predicted_rot
    1962              :       REAL(KIND=dp), DIMENSION(k)                        :: occupation
    1963              :       REAL(KIND=dp), DIMENSION(k), TARGET                :: rotation_evals
    1964              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    1965              :       TYPE(dbcsr_distribution_type)                      :: dist_kk, dist_nk, dist_nn
    1966           22 :       TYPE(dbcsr_type), DIMENSION(10), TARGET            :: rot_matrix
    1967            6 :       TYPE(dbcsr_type), DIMENSION(2), TARGET             :: nk_buffer
    1968            8 :       TYPE(dbcsr_type), DIMENSION(3), TARGET             :: nonsym_buffer
    1969           10 :       TYPE(dbcsr_type), DIMENSION(4), TARGET             :: sym_buffer
    1970              :       TYPE(dbcsr_type), POINTER                          :: c_im_p, c_re_p, hc_im_p, hc_re_p, &
    1971              :                                                             s_im_p, s_re_p
    1972              :       TYPE(dbcsr_type), TARGET                           :: c_im, c_re, f_im, f_re, g_im, g_re, &
    1973              :                                                             hc_im, hc_re, s_im, s_re, sx_im, &
    1974              :                                                             sx_re, x_im, x_re
    1975              :       TYPE(mp_para_env_type), POINTER                    :: local_para_env
    1976          324 :       TYPE(qs_ot_type)                                   :: rotation_env
    1977              : 
    1978            2 :       NULLIFY (blacs_env, c_im_p, c_re_p, hc_im_p, hc_re_p, local_para_env, &
    1979            2 :                s_im_p, s_re_p)
    1980              : 
    1981              :       x_dense(:, 1) = [CMPLX(1.05_dp, 0.12_dp, KIND=dp), CMPLX(0.18_dp, -0.27_dp, KIND=dp), &
    1982            8 :                        CMPLX(-0.14_dp, 0.16_dp, KIND=dp)]
    1983              :       x_dense(:, 2) = [CMPLX(0.26_dp, 0.31_dp, KIND=dp), CMPLX(0.94_dp, -0.17_dp, KIND=dp), &
    1984            8 :                        CMPLX(0.21_dp, 0.13_dp, KIND=dp)]
    1985              :       direction_x(:, 1) = [CMPLX(0.11_dp, -0.07_dp, KIND=dp), CMPLX(-0.16_dp, 0.09_dp, KIND=dp), &
    1986            8 :                            CMPLX(0.05_dp, 0.14_dp, KIND=dp)]
    1987              :       direction_x(:, 2) = [CMPLX(-0.08_dp, 0.15_dp, KIND=dp), CMPLX(0.13_dp, 0.04_dp, KIND=dp), &
    1988            8 :                            CMPLX(-0.10_dp, -0.06_dp, KIND=dp)]
    1989              : 
    1990              :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1991            2 :       generator(1, 1) = CMPLX(0.0_dp, 0.19_dp, KIND=dp)
    1992            2 :       generator(2, 2) = CMPLX(0.0_dp, -0.08_dp, KIND=dp)
    1993            2 :       generator(1, 2) = CMPLX(0.23_dp, -0.11_dp, KIND=dp)
    1994            2 :       generator(2, 1) = -CONJG(generator(1, 2))
    1995              :       direction_a(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    1996            2 :       direction_a(1, 1) = CMPLX(0.0_dp, -0.07_dp, KIND=dp)
    1997            2 :       direction_a(2, 2) = CMPLX(0.0_dp, 0.05_dp, KIND=dp)
    1998            2 :       direction_a(1, 2) = CMPLX(-0.09_dp, 0.06_dp, KIND=dp)
    1999            2 :       direction_a(2, 1) = -CONJG(direction_a(1, 2))
    2000              : 
    2001            2 :       hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2002            2 :       hamiltonian(1, 1) = CMPLX(0.65_dp, 0.0_dp, KIND=dp)
    2003            2 :       hamiltonian(2, 2) = CMPLX(-0.35_dp, 0.0_dp, KIND=dp)
    2004            2 :       hamiltonian(3, 3) = CMPLX(1.15_dp, 0.0_dp, KIND=dp)
    2005            2 :       hamiltonian(1, 2) = CMPLX(0.18_dp, 0.12_dp, KIND=dp)
    2006            2 :       hamiltonian(1, 3) = CMPLX(-0.09_dp, 0.07_dp, KIND=dp)
    2007            2 :       hamiltonian(2, 3) = CMPLX(0.14_dp, -0.17_dp, KIND=dp)
    2008            8 :       DO i = 1, n
    2009           14 :          DO j = i + 1, n
    2010           12 :             hamiltonian(j, i) = CONJG(hamiltonian(i, j))
    2011              :          END DO
    2012              :       END DO
    2013            2 :       overlap(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2014            8 :       DO i = 1, n
    2015            8 :          overlap(i, i) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2016              :       END DO
    2017            2 :       occupation = [1.0_dp, 0.37_dp]
    2018              : 
    2019           38 :       CALL dense_inverse_sqrt(MATMUL(CONJG(TRANSPOSE(x_dense)), x_dense), inverse_sqrt)
    2020           38 :       q_dense = MATMUL(x_dense, inverse_sqrt)
    2021            2 :       rotation = dense_antihermitian_exp(generator)
    2022           38 :       c_reference = MATMUL(q_dense, rotation)
    2023              : 
    2024            2 :       ALLOCATE (local_para_env)
    2025            2 :       CALL local_para_env%from_dup(mp_comm_self)
    2026            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=local_para_env)
    2027              : 
    2028              :       ALLOCATE (pgrid(0:0, 0:0), row_dist(1), col_dist(1), row_n(1), row_k(1), &
    2029            2 :                 col_n(1), col_k(1))
    2030            6 :       pgrid(:, :) = 0
    2031            4 :       row_dist(:) = 0
    2032            4 :       col_dist(:) = 0
    2033            4 :       row_n(:) = n
    2034            4 :       col_n(:) = n
    2035            4 :       row_k(:) = k
    2036            4 :       col_k(:) = k
    2037              :       CALL dbcsr_distribution_new(dist_nk, group=mp_comm_self%get_handle(), pgrid=pgrid, &
    2038            2 :                                   row_dist=row_dist, col_dist=col_dist)
    2039              :       CALL dbcsr_distribution_new(dist_nn, group=mp_comm_self%get_handle(), pgrid=pgrid, &
    2040            2 :                                   row_dist=row_dist, col_dist=col_dist)
    2041              :       CALL dbcsr_distribution_new(dist_kk, group=mp_comm_self%get_handle(), pgrid=pgrid, &
    2042            2 :                                   row_dist=row_dist, col_dist=col_dist)
    2043              : 
    2044            2 :       CALL create_one_block(x_re, 'rotation_ref_x_re', dist_nk, row_n, col_k)
    2045            2 :       CALL create_one_block(x_im, 'rotation_ref_x_im', dist_nk, row_n, col_k)
    2046            2 :       CALL create_one_block(sx_re, 'rotation_ref_sx_re', dist_nk, row_n, col_k)
    2047            2 :       CALL create_one_block(sx_im, 'rotation_ref_sx_im', dist_nk, row_n, col_k)
    2048            2 :       CALL create_one_block(c_re, 'rotation_ref_c_re', dist_nk, row_n, col_k)
    2049            2 :       CALL create_one_block(c_im, 'rotation_ref_c_im', dist_nk, row_n, col_k)
    2050            2 :       CALL create_one_block(hc_re, 'rotation_ref_hc_re', dist_nk, row_n, col_k)
    2051            2 :       CALL create_one_block(hc_im, 'rotation_ref_hc_im', dist_nk, row_n, col_k)
    2052            2 :       CALL create_one_block(g_re, 'rotation_ref_g_re', dist_nk, row_n, col_k)
    2053            2 :       CALL create_one_block(g_im, 'rotation_ref_g_im', dist_nk, row_n, col_k)
    2054            2 :       CALL create_one_block(s_re, 'rotation_ref_s_re', dist_nn, row_n, col_n)
    2055            2 :       CALL create_one_block(s_im, 'rotation_ref_s_im', dist_nn, row_n, col_n)
    2056            2 :       CALL create_one_block(f_re, 'rotation_ref_f_re', dist_kk, row_k, col_k)
    2057            2 :       CALL create_one_block(f_im, 'rotation_ref_f_im', dist_kk, row_k, col_k)
    2058           10 :       DO i = 1, SIZE(sym_buffer)
    2059           10 :          CALL create_one_block(sym_buffer(i), 'rotation_ref_sym', dist_kk, row_k, col_k)
    2060              :       END DO
    2061            8 :       DO i = 1, SIZE(nonsym_buffer)
    2062            8 :          CALL create_one_block(nonsym_buffer(i), 'rotation_ref_nonsym', dist_kk, row_k, col_k)
    2063              :       END DO
    2064            6 :       DO i = 1, SIZE(nk_buffer)
    2065            6 :          CALL create_one_block(nk_buffer(i), 'rotation_ref_nk', dist_nk, row_n, col_k)
    2066              :       END DO
    2067           22 :       DO i = 1, SIZE(rot_matrix)
    2068           22 :          CALL create_one_block(rot_matrix(i), 'rotation_ref_rot', dist_kk, row_k, col_k)
    2069              :       END DO
    2070              : 
    2071            2 :       CALL put_complex_pair(x_re, x_im, x_dense)
    2072            2 :       CALL put_complex_pair(s_re, s_im, overlap)
    2073            2 :       CALL put_complex_pair(rot_matrix(3), rot_matrix(4), generator)
    2074              : 
    2075            2 :       rotation_env%has_complex_kpoint_state = .TRUE.
    2076            2 :       rotation_env%settings%do_rotation = .TRUE.
    2077            2 :       rotation_env%settings%eps_irac_filter_matrix = 0.0_dp
    2078            2 :       rotation_env%settings%ot_method = 'LBFG'
    2079            2 :       rotation_env%para_env => local_para_env
    2080            2 :       rotation_env%blacs_env => blacs_env
    2081            2 :       rotation_env%matrix_x => x_re
    2082            2 :       rotation_env%matrix_x_im => x_im
    2083            2 :       rotation_env%matrix_sx => sx_re
    2084            2 :       rotation_env%matrix_sx_im => sx_im
    2085            2 :       rotation_env%matrix_gx => g_re
    2086            2 :       rotation_env%matrix_gx_im => g_im
    2087            2 :       rotation_env%matrix_ref_inv_sqrt => f_re
    2088            2 :       rotation_env%matrix_ref_inv_sqrt_im => f_im
    2089            2 :       rotation_env%buf1_k_k_sym => sym_buffer(1)
    2090            2 :       rotation_env%buf2_k_k_sym => sym_buffer(2)
    2091            2 :       rotation_env%buf3_k_k_sym => sym_buffer(3)
    2092            2 :       rotation_env%buf4_k_k_sym => sym_buffer(4)
    2093            2 :       rotation_env%buf1_k_k_nosym => nonsym_buffer(1)
    2094            2 :       rotation_env%buf2_k_k_nosym => nonsym_buffer(2)
    2095            2 :       rotation_env%buf3_k_k_nosym => nonsym_buffer(3)
    2096            2 :       rotation_env%buf1_n_k => nk_buffer(1)
    2097            2 :       rotation_env%buf1_n_k_dp => nk_buffer(2)
    2098            2 :       rotation_env%rot_mat_u => rot_matrix(1)
    2099            2 :       rotation_env%rot_mat_u_im => rot_matrix(2)
    2100            2 :       rotation_env%rot_mat_x => rot_matrix(3)
    2101            2 :       rotation_env%rot_mat_x_im => rot_matrix(4)
    2102            2 :       rotation_env%rot_mat_dedu => rot_matrix(5)
    2103            2 :       rotation_env%rot_mat_dedu_im => rot_matrix(6)
    2104            2 :       rotation_env%rot_mat_gx => rot_matrix(7)
    2105            2 :       rotation_env%rot_mat_gx_im => rot_matrix(8)
    2106            2 :       rotation_env%rot_mat_evec_re => rot_matrix(9)
    2107            2 :       rotation_env%rot_mat_evec_im => rot_matrix(10)
    2108            2 :       rotation_env%rot_mat_evals => rotation_evals
    2109              : 
    2110            2 :       c_re_p => c_re
    2111            2 :       c_im_p => c_im
    2112            2 :       s_re_p => s_re
    2113            2 :       s_im_p => s_im
    2114            2 :       CALL qs_ot_get_orbitals_ref_complex(c_re_p, c_im_p, s_re_p, s_im_p, rotation_env)
    2115            2 :       CALL get_complex_pair(c_re, c_im, c_dense)
    2116           18 :       error_c = MAXVAL(ABS(c_dense - c_reference))
    2117              : 
    2118           70 :       hc_dense = 2.0_dp*MATMUL(hamiltonian, c_dense)
    2119            6 :       DO j = 1, k
    2120           18 :          hc_dense(:, j) = occupation(j)*hc_dense(:, j)
    2121              :       END DO
    2122            2 :       CALL put_complex_pair(hc_re, hc_im, hc_dense)
    2123            2 :       hc_re_p => hc_re
    2124            2 :       hc_im_p => hc_im
    2125            2 :       CALL qs_ot_get_derivative_ref_complex(hc_re_p, hc_im_p, rotation_env)
    2126            2 :       CALL get_complex_pair(g_re, g_im, gradient_x)
    2127            2 :       CALL get_complex_pair(rot_matrix(7), rot_matrix(8), gradient_a)
    2128              : 
    2129            2 :       hstep = 1.0E-6_dp
    2130              :       fd_ref = (rotated_polar_energy(x_dense + hstep*direction_x, generator, &
    2131              :                                      hamiltonian, occupation) - &
    2132              :                 rotated_polar_energy(x_dense - hstep*direction_x, generator, &
    2133           34 :                                      hamiltonian, occupation))/(2.0_dp*hstep)
    2134           18 :       predicted_ref = REAL(SUM(CONJG(gradient_x)*direction_x), KIND=dp)
    2135            2 :       error_ref = ABS(predicted_ref - fd_ref)
    2136              : 
    2137              :       fd_rot = (rotated_polar_energy(x_dense, generator + hstep*direction_a, &
    2138              :                                      hamiltonian, occupation) - &
    2139              :                 rotated_polar_energy(x_dense, generator - hstep*direction_a, &
    2140           26 :                                      hamiltonian, occupation))/(2.0_dp*hstep)
    2141              :       predicted_rot = 0.5_dp*(SUM(REAL(gradient_a, KIND=dp)*REAL(direction_a, KIND=dp)) + &
    2142           26 :                               SUM(AIMAG(gradient_a)*AIMAG(direction_a)))
    2143            2 :       error_rot = ABS(predicted_rot - fd_rot)
    2144              : 
    2145            2 :       IF (error_c > 5.0E-11_dp .OR. error_ref > 5.0E-8_dp .OR. &
    2146            0 :           error_rot > 5.0E-8_dp) nfail = nfail + 1
    2147            2 :       IF (para_env%is_source()) THEN
    2148            1 :          WRITE (io_unit, '(A,1X,ES13.6)') 'complex finite REF rotation orbital error', error_c
    2149            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex finite REF slope fd/predicted/error', &
    2150            2 :             fd_ref, predicted_ref, error_ref
    2151            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex finite rotation slope fd/predicted/error', &
    2152            2 :             fd_rot, predicted_rot, error_rot
    2153              :       END IF
    2154              : 
    2155           22 :       DO i = 1, SIZE(rot_matrix)
    2156           22 :          CALL dbcsr_release(rot_matrix(i))
    2157              :       END DO
    2158            6 :       DO i = 1, SIZE(nk_buffer)
    2159            6 :          CALL dbcsr_release(nk_buffer(i))
    2160              :       END DO
    2161            8 :       DO i = 1, SIZE(nonsym_buffer)
    2162            8 :          CALL dbcsr_release(nonsym_buffer(i))
    2163              :       END DO
    2164           10 :       DO i = 1, SIZE(sym_buffer)
    2165           10 :          CALL dbcsr_release(sym_buffer(i))
    2166              :       END DO
    2167            2 :       CALL dbcsr_release(f_im)
    2168            2 :       CALL dbcsr_release(f_re)
    2169            2 :       CALL dbcsr_release(s_im)
    2170            2 :       CALL dbcsr_release(s_re)
    2171            2 :       CALL dbcsr_release(g_im)
    2172            2 :       CALL dbcsr_release(g_re)
    2173            2 :       CALL dbcsr_release(hc_im)
    2174            2 :       CALL dbcsr_release(hc_re)
    2175            2 :       CALL dbcsr_release(c_im)
    2176            2 :       CALL dbcsr_release(c_re)
    2177            2 :       CALL dbcsr_release(sx_im)
    2178            2 :       CALL dbcsr_release(sx_re)
    2179            2 :       CALL dbcsr_release(x_im)
    2180            2 :       CALL dbcsr_release(x_re)
    2181            2 :       CALL dbcsr_distribution_release(dist_kk)
    2182            2 :       CALL dbcsr_distribution_release(dist_nn)
    2183            2 :       CALL dbcsr_distribution_release(dist_nk)
    2184            2 :       DEALLOCATE (pgrid, row_dist, col_dist, row_n, row_k, col_n, col_k)
    2185            2 :       CALL cp_blacs_env_release(blacs_env)
    2186            2 :       CALL mp_para_env_release(local_para_env)
    2187              : 
    2188           14 :    END SUBROUTINE test_complex_ref_rotation
    2189              : 
    2190              : ! **************************************************************************************************
    2191              : !> \brief Check the finite complex rotation Frechet derivative against central differences.
    2192              : !> \param para_env parallel environment
    2193              : !> \param nfail accumulated number of failures
    2194              : ! **************************************************************************************************
    2195            2 :    SUBROUTINE test_complex_rotation_frechet(para_env, nfail)
    2196              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2197              :       INTEGER, INTENT(INOUT)                             :: nfail
    2198              : 
    2199              :       INTEGER, PARAMETER                                 :: m = 3
    2200              : 
    2201              :       COMPLEX(KIND=dp), DIMENSION(m, m)                  :: direction, fd, generator, predicted
    2202              :       INTEGER                                            :: i, j
    2203              :       REAL(KIND=dp)                                      :: error, hstep
    2204              :       REAL(KIND=dp), DIMENSION(m)                        :: evals
    2205              : 
    2206            2 :       evals = [-0.7_dp, 0.2000003_dp, 1.1_dp]
    2207            2 :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2208            8 :       DO i = 1, m
    2209            8 :          generator(i, i) = CMPLX(0.0_dp, -evals(i), KIND=dp)
    2210              :       END DO
    2211              :       direction(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2212            2 :       direction(1, 2) = CMPLX(0.17_dp, -0.09_dp, KIND=dp)
    2213            2 :       direction(2, 1) = -CONJG(direction(1, 2))
    2214            2 :       direction(1, 3) = CMPLX(-0.06_dp, 0.13_dp, KIND=dp)
    2215            2 :       direction(3, 1) = -CONJG(direction(1, 3))
    2216            2 :       direction(2, 3) = CMPLX(0.11_dp, 0.04_dp, KIND=dp)
    2217            2 :       direction(3, 2) = -CONJG(direction(2, 3))
    2218            2 :       direction(1, 1) = CMPLX(0.0_dp, 0.08_dp, KIND=dp)
    2219            2 :       direction(2, 2) = CMPLX(0.0_dp, -0.03_dp, KIND=dp)
    2220            2 :       direction(3, 3) = CMPLX(0.0_dp, 0.05_dp, KIND=dp)
    2221              : 
    2222            8 :       DO i = 1, m
    2223           26 :          DO j = 1, m
    2224              :             predicted(i, j) = qs_ot_complex_exp_frechet_kernel(evals(i), evals(j))* &
    2225           24 :                               direction(i, j)
    2226              :          END DO
    2227              :       END DO
    2228            2 :       hstep = 1.0E-6_dp
    2229              :       fd = (dense_antihermitian_exp(generator + hstep*direction) - &
    2230           74 :             dense_antihermitian_exp(generator - hstep*direction))/(2.0_dp*hstep)
    2231           26 :       error = MAXVAL(ABS(fd - predicted))
    2232            2 :       IF (error > 5.0E-9_dp) nfail = nfail + 1
    2233            2 :       IF (para_env%is_source()) THEN
    2234            1 :          WRITE (io_unit, '(A,1X,ES13.6)') 'complex rotation Frechet finite-difference error', error
    2235              :       END IF
    2236              : 
    2237            2 :    END SUBROUTINE test_complex_rotation_frechet
    2238              : 
    2239              : ! **************************************************************************************************
    2240              : !> \brief Check union handling for distinct real/imaginary DBCSR block patterns.
    2241              : !> \param para_env parallel environment
    2242              : !> \param nfail accumulated number of failures
    2243              : ! **************************************************************************************************
    2244            2 :    SUBROUTINE test_sparse_frechet_patterns(para_env, nfail)
    2245              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2246              :       INTEGER, INTENT(INOUT)                             :: nfail
    2247              : 
    2248              :       COMPLEX(KIND=dp)                                   :: expected, value
    2249            2 :       INTEGER, DIMENSION(:), POINTER                     :: col_dist, col_size, row_dist, row_size
    2250              :       INTEGER, DIMENSION(:, :), POINTER                  :: pgrid
    2251              :       LOGICAL                                            :: found
    2252              :       REAL(KIND=dp)                                      :: error
    2253              :       REAL(KIND=dp), DIMENSION(2)                        :: evals
    2254            2 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block
    2255              :       TYPE(dbcsr_distribution_type)                      :: dist
    2256              :       TYPE(dbcsr_type)                                   :: inner_im, inner_re, outer_im, outer_re
    2257              : 
    2258            2 :       ALLOCATE (pgrid(0:0, 0:0), row_dist(2), col_dist(2), row_size(2), col_size(2))
    2259            6 :       pgrid(:, :) = 0
    2260            6 :       row_dist(:) = 0
    2261            6 :       col_dist(:) = 0
    2262            6 :       row_size(:) = 1
    2263            6 :       col_size(:) = 1
    2264              :       CALL dbcsr_distribution_new(dist, group=mp_comm_self%get_handle(), pgrid=pgrid, &
    2265            2 :                                   row_dist=row_dist, col_dist=col_dist)
    2266            2 :       CALL dbcsr_create(inner_re, 'frechet_sparse_re', dist, dbcsr_type_no_symmetry, row_size, col_size)
    2267            2 :       CALL dbcsr_reserve_blocks(inner_re, rows=[1], cols=[2])
    2268            2 :       CALL dbcsr_finalize(inner_re)
    2269            2 :       CALL dbcsr_create(inner_im, 'frechet_sparse_im', dist, dbcsr_type_no_symmetry, row_size, col_size)
    2270            2 :       CALL dbcsr_reserve_blocks(inner_im, rows=[2], cols=[1])
    2271            2 :       CALL dbcsr_finalize(inner_im)
    2272            2 :       CALL dbcsr_put_block(inner_re, 1, 2, RESHAPE([0.37_dp], [1, 1]))
    2273            2 :       CALL dbcsr_put_block(inner_im, 2, 1, RESHAPE([-0.21_dp], [1, 1]))
    2274              : 
    2275            2 :       evals = [-0.4_dp, 0.9_dp]
    2276            2 :       CALL qs_ot_apply_complex_frechet_dbcsr(evals, inner_re, inner_im, outer_re, outer_im)
    2277              :       error = 0.0_dp
    2278            2 :       expected = qs_ot_complex_exp_frechet_kernel(evals(1), evals(2))*CMPLX(0.37_dp, 0.0_dp, KIND=dp)
    2279            2 :       value = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2280            2 :       CALL dbcsr_get_block_p(outer_re, 1, 2, block, found)
    2281            2 :       IF (found) value = value + CMPLX(block(1, 1), 0.0_dp, KIND=dp)
    2282            2 :       CALL dbcsr_get_block_p(outer_im, 1, 2, block, found)
    2283            2 :       IF (found) value = value + CMPLX(0.0_dp, block(1, 1), KIND=dp)
    2284            2 :       error = MAX(error, ABS(value - expected))
    2285              : 
    2286            2 :       expected = qs_ot_complex_exp_frechet_kernel(evals(2), evals(1))*CMPLX(0.0_dp, -0.21_dp, KIND=dp)
    2287            2 :       value = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2288            2 :       CALL dbcsr_get_block_p(outer_re, 2, 1, block, found)
    2289            2 :       IF (found) value = value + CMPLX(block(1, 1), 0.0_dp, KIND=dp)
    2290            2 :       CALL dbcsr_get_block_p(outer_im, 2, 1, block, found)
    2291            2 :       IF (found) value = value + CMPLX(0.0_dp, block(1, 1), KIND=dp)
    2292            2 :       error = MAX(error, ABS(value - expected))
    2293            2 :       IF (error > 5.0E-14_dp) nfail = nfail + 1
    2294            2 :       IF (para_env%is_source()) THEN
    2295            1 :          WRITE (io_unit, '(A,1X,ES13.6)') 'sparse complex Frechet block-union error', error
    2296              :       END IF
    2297              : 
    2298            2 :       CALL dbcsr_release(outer_im)
    2299            2 :       CALL dbcsr_release(outer_re)
    2300            2 :       CALL dbcsr_release(inner_im)
    2301            2 :       CALL dbcsr_release(inner_re)
    2302            2 :       CALL dbcsr_distribution_release(dist)
    2303            2 :       DEALLOCATE (pgrid, row_dist, col_dist, row_size, col_size)
    2304              : 
    2305            2 :    END SUBROUTINE test_sparse_frechet_patterns
    2306              : ! **************************************************************************************************
    2307              : !> \brief Check the finite complex STRICT chart and its analytic derivative.
    2308              : !> \param dist_nk distribution for orbital-shaped matrices
    2309              : !> \param dist_kk distribution for occupied-space matrices
    2310              : !> \param row_n AO block sizes
    2311              : !> \param row_k occupied-space row block sizes
    2312              : !> \param col_k occupied-space column block sizes
    2313              : !> \param para_env parallel environment
    2314              : !> \param nfail accumulated number of failures
    2315              : ! **************************************************************************************************
    2316            2 :    SUBROUTINE test_complex_strict_derivative(dist_nk, dist_kk, row_n, row_k, col_k, para_env, nfail)
    2317              :       TYPE(dbcsr_distribution_type), INTENT(IN)          :: dist_nk, dist_kk
    2318              :       INTEGER, DIMENSION(:), INTENT(INOUT), POINTER      :: row_n, row_k, col_k
    2319              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2320              :       INTEGER, INTENT(INOUT)                             :: nfail
    2321              : 
    2322              :       CHARACTER(LEN=32)                                  :: name
    2323              :       COMPLEX(KIND=dp), DIMENSION(k, k)                  :: direction_a, generator, gradient_a, &
    2324              :                                                             inverse_sqrt, rotation
    2325              :       COMPLEX(KIND=dp), DIMENSION(n, k)                  :: c0, direction, gradient, hc, orbitals, &
    2326              :                                                             orbitals_base, sc0, sx, x
    2327              :       COMPLEX(KIND=dp), DIMENSION(n, n)                  :: hamiltonian, overlap
    2328              :       INTEGER                                            :: i
    2329              :       LOGICAL                                            :: found
    2330              :       REAL(KIND=dp)                                      :: error, error_rot, fd_slope, &
    2331              :                                                             fd_slope_rot, hstep, ortho_error, &
    2332              :                                                             slope, slope_rot
    2333            2 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block
    2334              :       REAL(KIND=dp), DIMENSION(k)                        :: occupation
    2335              :       REAL(KIND=dp), DIMENSION(k), TARGET                :: rotation_evals
    2336              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    2337           22 :       TYPE(dbcsr_type), DIMENSION(10), TARGET            :: rot_matrix
    2338           32 :       TYPE(dbcsr_type), DIMENSION(15), TARGET            :: nk
    2339           52 :       TYPE(dbcsr_type), DIMENSION(25), TARGET            :: kk
    2340              :       TYPE(dbcsr_type), POINTER                          :: hc_im_p, hc_re_p, sx_im_p, sx_re_p, &
    2341              :                                                             x_im_p, x_re_p
    2342              :       TYPE(mp_para_env_type), POINTER                    :: strict_para_env
    2343          324 :       TYPE(qs_ot_type)                                   :: strict_env
    2344              : 
    2345            2 :       NULLIFY (blacs_env, hc_im_p, hc_re_p, strict_para_env, x_im_p, x_re_p, sx_im_p, sx_re_p)
    2346            2 :       ALLOCATE (strict_para_env)
    2347            2 :       CALL strict_para_env%from_dup(mp_comm_self)
    2348            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=strict_para_env)
    2349           32 :       DO i = 1, SIZE(nk)
    2350           30 :          WRITE (name, '(A,I0)') 'complex_strict_nk_', i
    2351           32 :          CALL create_one_block(nk(i), TRIM(name), dist_nk, row_n, col_k)
    2352              :       END DO
    2353           52 :       DO i = 1, SIZE(kk)
    2354           50 :          WRITE (name, '(A,I0)') 'complex_strict_kk_', i
    2355           52 :          CALL create_one_block(kk(i), TRIM(name), dist_kk, row_k, col_k)
    2356              :       END DO
    2357           22 :       DO i = 1, SIZE(rot_matrix)
    2358           20 :          WRITE (name, '(A,I0)') 'complex_strict_rot_', i
    2359           22 :          CALL create_one_block(rot_matrix(i), TRIM(name), dist_kk, row_k, col_k)
    2360              :       END DO
    2361              : 
    2362              :       overlap(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2363            2 :       overlap(1, 1) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2364            2 :       overlap(2, 2) = CMPLX(1.2_dp, 0.0_dp, KIND=dp)
    2365            2 :       overlap(3, 3) = CMPLX(1.4_dp, 0.0_dp, KIND=dp)
    2366            2 :       overlap(1, 2) = CMPLX(0.06_dp, 0.03_dp, KIND=dp)
    2367            2 :       overlap(2, 1) = CONJG(overlap(1, 2))
    2368            2 :       overlap(1, 3) = CMPLX(-0.04_dp, 0.02_dp, KIND=dp)
    2369            2 :       overlap(3, 1) = CONJG(overlap(1, 3))
    2370            2 :       overlap(2, 3) = CMPLX(0.05_dp, -0.01_dp, KIND=dp)
    2371            2 :       overlap(3, 2) = CONJG(overlap(2, 3))
    2372              : 
    2373              :       c0(:, 1) = [CMPLX(1.0_dp, 0.10_dp, KIND=dp), CMPLX(0.12_dp, -0.08_dp, KIND=dp), &
    2374            8 :                   CMPLX(-0.07_dp, 0.04_dp, KIND=dp)]
    2375              :       c0(:, 2) = [CMPLX(0.09_dp, 0.05_dp, KIND=dp), CMPLX(0.85_dp, -0.12_dp, KIND=dp), &
    2376            8 :                   CMPLX(0.11_dp, 0.06_dp, KIND=dp)]
    2377           90 :       CALL dense_inverse_sqrt(MATMUL(CONJG(TRANSPOSE(c0)), MATMUL(overlap, c0)), inverse_sqrt)
    2378           38 :       c0 = MATMUL(c0, inverse_sqrt)
    2379              : 
    2380            2 :       x(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2381            2 :       x(3, 1) = CMPLX(0.23_dp, 0.17_dp, KIND=dp)
    2382            2 :       x(3, 2) = CMPLX(-0.14_dp, 0.21_dp, KIND=dp)
    2383          144 :       x = x - MATMUL(c0, MATMUL(CONJG(TRANSPOSE(c0)), MATMUL(overlap, x)))
    2384            2 :       direction(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2385            2 :       direction(3, 1) = CMPLX(0.11_dp, -0.09_dp, KIND=dp)
    2386            2 :       direction(3, 2) = CMPLX(0.07_dp, 0.13_dp, KIND=dp)
    2387          144 :       direction = direction - MATMUL(c0, MATMUL(CONJG(TRANSPOSE(c0)), MATMUL(overlap, direction)))
    2388              : 
    2389              :       hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2390            2 :       hamiltonian(1, 1) = CMPLX(-0.70_dp, 0.0_dp, KIND=dp)
    2391            2 :       hamiltonian(2, 2) = CMPLX(-0.20_dp, 0.0_dp, KIND=dp)
    2392            2 :       hamiltonian(3, 3) = CMPLX(0.90_dp, 0.0_dp, KIND=dp)
    2393            2 :       hamiltonian(1, 2) = CMPLX(0.08_dp, 0.04_dp, KIND=dp)
    2394            2 :       hamiltonian(2, 1) = CONJG(hamiltonian(1, 2))
    2395            2 :       hamiltonian(1, 3) = CMPLX(-0.12_dp, 0.07_dp, KIND=dp)
    2396            2 :       hamiltonian(3, 1) = CONJG(hamiltonian(1, 3))
    2397            2 :       hamiltonian(2, 3) = CMPLX(0.05_dp, -0.09_dp, KIND=dp)
    2398            2 :       hamiltonian(3, 2) = CONJG(hamiltonian(2, 3))
    2399              : 
    2400              :       generator(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2401            2 :       generator(1, 1) = CMPLX(0.0_dp, 0.16_dp, KIND=dp)
    2402            2 :       generator(2, 2) = CMPLX(0.0_dp, -0.09_dp, KIND=dp)
    2403            2 :       generator(1, 2) = CMPLX(0.18_dp, -0.07_dp, KIND=dp)
    2404            2 :       generator(2, 1) = -CONJG(generator(1, 2))
    2405              :       direction_a(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2406            2 :       direction_a(1, 1) = CMPLX(0.0_dp, -0.05_dp, KIND=dp)
    2407            2 :       direction_a(2, 2) = CMPLX(0.0_dp, 0.04_dp, KIND=dp)
    2408            2 :       direction_a(1, 2) = CMPLX(-0.08_dp, 0.06_dp, KIND=dp)
    2409            2 :       direction_a(2, 1) = -CONJG(direction_a(1, 2))
    2410            2 :       occupation = [1.0_dp, 0.34_dp]
    2411              : 
    2412            2 :       CALL strict_orbitals(c0, x, overlap, orbitals_base)
    2413            2 :       rotation = dense_antihermitian_exp(generator)
    2414           38 :       orbitals = MATMUL(orbitals_base, rotation)
    2415           70 :       hc = 2.0_dp*MATMUL(hamiltonian, orbitals)
    2416            6 :       DO i = 1, k
    2417           18 :          hc(:, i) = occupation(i)*hc(:, i)
    2418              :       END DO
    2419           54 :       sx = MATMUL(overlap, x)
    2420           54 :       sc0 = MATMUL(overlap, c0)
    2421            2 :       CALL put_complex_pair(nk(1), nk(2), x)
    2422            2 :       CALL put_complex_pair(nk(3), nk(4), sx)
    2423            2 :       CALL put_complex_pair(nk(5), nk(6), hc)
    2424            2 :       CALL put_complex_pair(nk(9), nk(10), c0)
    2425            2 :       CALL put_complex_pair(nk(11), nk(12), sc0)
    2426            2 :       CALL put_complex_pair(rot_matrix(3), rot_matrix(4), generator)
    2427              : 
    2428            2 :       strict_env%has_complex_kpoint_state = .TRUE.
    2429            2 :       strict_env%settings%ot_algorithm = 'TOD'
    2430            2 :       strict_env%settings%do_rotation = .TRUE.
    2431            2 :       strict_env%para_env => strict_para_env
    2432            2 :       strict_env%blacs_env => blacs_env
    2433            2 :       strict_env%matrix_x => nk(1)
    2434            2 :       strict_env%matrix_x_im => nk(2)
    2435            2 :       strict_env%matrix_sx => nk(3)
    2436            2 :       strict_env%matrix_sx_im => nk(4)
    2437            2 :       strict_env%matrix_gx => nk(7)
    2438            2 :       strict_env%matrix_gx_im => nk(8)
    2439            2 :       strict_env%matrix_c0 => nk(9)
    2440            2 :       strict_env%matrix_c0_im => nk(10)
    2441            2 :       strict_env%matrix_sc0 => nk(11)
    2442            2 :       strict_env%matrix_sc0_im => nk(12)
    2443            2 :       strict_env%matrix_buf_nk => nk(13)
    2444            2 :       strict_env%matrix_buf_nk_im => nk(14)
    2445            2 :       strict_env%matrix_tmp_nk => nk(15)
    2446            2 :       strict_env%matrix_p => kk(1)
    2447            2 :       strict_env%matrix_p_im => kk(2)
    2448            2 :       strict_env%matrix_r => kk(3)
    2449            2 :       strict_env%matrix_r_im => kk(4)
    2450            2 :       strict_env%matrix_sinp => kk(5)
    2451            2 :       strict_env%matrix_sinp_im => kk(6)
    2452            2 :       strict_env%matrix_cosp => kk(7)
    2453            2 :       strict_env%matrix_cosp_im => kk(8)
    2454            2 :       strict_env%matrix_sinp_b => kk(9)
    2455            2 :       strict_env%matrix_cosp_b => kk(10)
    2456            2 :       strict_env%matrix_buf1 => kk(11)
    2457            2 :       strict_env%matrix_buf1_im => kk(12)
    2458            2 :       strict_env%matrix_buf2 => kk(13)
    2459            2 :       strict_env%matrix_buf2_im => kk(14)
    2460            2 :       strict_env%matrix_buf3 => kk(15)
    2461            2 :       strict_env%matrix_buf3_im => kk(16)
    2462            2 :       strict_env%matrix_buf4 => kk(17)
    2463            2 :       strict_env%matrix_buf4_im => kk(18)
    2464            2 :       strict_env%matrix_os => kk(19)
    2465            2 :       strict_env%matrix_os_im => kk(20)
    2466            2 :       strict_env%matrix_buf1_ortho => kk(21)
    2467            2 :       strict_env%matrix_buf1_ortho_im => kk(22)
    2468            2 :       strict_env%matrix_buf2_ortho => kk(23)
    2469            2 :       strict_env%matrix_buf2_ortho_im => kk(24)
    2470            2 :       strict_env%matrix_tmp_ortho => kk(25)
    2471            2 :       strict_env%rot_mat_u => rot_matrix(1)
    2472            2 :       strict_env%rot_mat_u_im => rot_matrix(2)
    2473            2 :       strict_env%rot_mat_x => rot_matrix(3)
    2474            2 :       strict_env%rot_mat_x_im => rot_matrix(4)
    2475            2 :       strict_env%rot_mat_dedu => rot_matrix(5)
    2476            2 :       strict_env%rot_mat_dedu_im => rot_matrix(6)
    2477            2 :       strict_env%rot_mat_gx => rot_matrix(7)
    2478            2 :       strict_env%rot_mat_gx_im => rot_matrix(8)
    2479            2 :       strict_env%rot_mat_evec_re => rot_matrix(9)
    2480            2 :       strict_env%rot_mat_evec_im => rot_matrix(10)
    2481            2 :       strict_env%rot_mat_evals => rotation_evals
    2482            2 :       ALLOCATE (strict_env%evals(k), strict_env%dum(k))
    2483              : 
    2484            2 :       x_re_p => nk(1)
    2485            2 :       x_im_p => nk(2)
    2486            2 :       sx_re_p => nk(3)
    2487            2 :       sx_im_p => nk(4)
    2488            2 :       hc_re_p => nk(5)
    2489            2 :       hc_im_p => nk(6)
    2490            2 :       CALL qs_ot_get_p_complex(x_re_p, x_im_p, sx_re_p, sx_im_p, strict_env)
    2491            2 :       CALL qs_ot_get_derivative_complex(hc_re_p, hc_im_p, strict_env)
    2492              : 
    2493            2 :       gradient(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2494            2 :       CALL dbcsr_get_block_p(nk(7), 1, 1, block, found)
    2495           18 :       IF (found) gradient = gradient + CMPLX(block, 0.0_dp, KIND=dp)
    2496            2 :       CALL dbcsr_get_block_p(nk(8), 1, 1, block, found)
    2497           18 :       IF (found) gradient = gradient + CMPLX(0.0_dp, block, KIND=dp)
    2498            2 :       CALL get_complex_pair(rot_matrix(7), rot_matrix(8), gradient_a)
    2499              : 
    2500            2 :       hstep = 1.0E-6_dp
    2501              :       fd_slope = (strict_rotated_energy(c0, x + hstep*direction, generator, overlap, &
    2502              :                                         hamiltonian, occupation) - &
    2503              :                   strict_rotated_energy(c0, x - hstep*direction, generator, overlap, &
    2504           34 :                                         hamiltonian, occupation))/(2.0_dp*hstep)
    2505           18 :       slope = REAL(SUM(CONJG(gradient)*direction), KIND=dp)
    2506            2 :       error = ABS(slope - fd_slope)
    2507              :       fd_slope_rot = (strict_rotated_energy(c0, x, generator + hstep*direction_a, overlap, &
    2508              :                                             hamiltonian, occupation) - &
    2509              :                       strict_rotated_energy(c0, x, generator - hstep*direction_a, overlap, &
    2510           26 :                                             hamiltonian, occupation))/(2.0_dp*hstep)
    2511              :       slope_rot = 0.5_dp*(SUM(REAL(gradient_a, KIND=dp)*REAL(direction_a, KIND=dp)) + &
    2512           26 :                           SUM(AIMAG(gradient_a)*AIMAG(direction_a)))
    2513            2 :       error_rot = ABS(slope_rot - fd_slope_rot)
    2514              :       ortho_error = MAXVAL(ABS(MATMUL(CONJG(TRANSPOSE(orbitals)), MATMUL(overlap, orbitals)) - &
    2515              :                                RESHAPE([CMPLX(1.0_dp, 0.0_dp, KIND=dp), &
    2516              :                                         CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2517              :                                         CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2518          102 :                                         CMPLX(1.0_dp, 0.0_dp, KIND=dp)], [k, k])))
    2519            2 :       IF (error > 5.0E-8_dp .OR. error_rot > 5.0E-8_dp .OR. &
    2520            0 :           ortho_error > 5.0E-13_dp) nfail = nfail + 1
    2521            2 :       IF (para_env%is_source()) THEN
    2522            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex finite-STRICT derivative: fd/predicted/error', &
    2523            2 :             fd_slope, slope, error
    2524            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex finite-STRICT rotation: fd/predicted/error', &
    2525            2 :             fd_slope_rot, slope_rot, error_rot
    2526            1 :          WRITE (io_unit, '(A,1X,ES13.6)') 'complex finite-STRICT orthonormality error', ortho_error
    2527              :       END IF
    2528              : 
    2529            2 :       DEALLOCATE (strict_env%evals, strict_env%dum)
    2530           22 :       DO i = SIZE(rot_matrix), 1, -1
    2531           22 :          CALL dbcsr_release(rot_matrix(i))
    2532              :       END DO
    2533           52 :       DO i = SIZE(kk), 1, -1
    2534           52 :          CALL dbcsr_release(kk(i))
    2535              :       END DO
    2536           32 :       DO i = SIZE(nk), 1, -1
    2537           32 :          CALL dbcsr_release(nk(i))
    2538              :       END DO
    2539            2 :       CALL cp_blacs_env_release(blacs_env)
    2540            2 :       CALL mp_para_env_release(strict_para_env)
    2541              : 
    2542            8 :    END SUBROUTINE test_complex_strict_derivative
    2543              : ! **************************************************************************************************
    2544              : !> \brief Check gauge invariance, Hermiticity, and positivity of the complex inverse operator.
    2545              : !> \param para_env parallel environment
    2546              : !> \param nfail accumulated number of failures
    2547              : ! **************************************************************************************************
    2548            2 :    SUBROUTINE test_complex_preconditioner_gauge(para_env, nfail)
    2549              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2550              :       INTEGER, INTENT(INOUT)                             :: nfail
    2551              : 
    2552              :       COMPLEX(KIND=dp)                                   :: value
    2553              :       COMPLEX(KIND=dp), DIMENSION(k)                     :: phases
    2554              :       COMPLEX(KIND=dp), DIMENSION(n, k)                  :: c0, c0_phase, c0_rot, gradient, &
    2555              :                                                             gradient_phase, gradient_rot, output, &
    2556              :                                                             output_phase, output_rot
    2557              :       COMPLEX(KIND=dp), DIMENSION(n, n) :: eigenvectors, full_all_vectors, h, inverse, &
    2558              :          inverse_rot, inverse_s, inverse_single, inverse_t, reference_s, reference_t, s, &
    2559              :          spectral_metric, t
    2560              :       INTEGER                                            :: i, j
    2561              :       REAL(KIND=dp) :: covariant_descent, covariant_gauge_error, full_all_descent, &
    2562              :          full_all_gauge_error, full_all_orthogonality_error, full_single_hermitian_error, &
    2563              :          gauge_error, hermitian_error, low_rank_descent, low_rank_gauge_error, s_error, t_error
    2564              :       REAL(KIND=dp), DIMENSION(k)                        :: c0_evals
    2565              :       REAL(KIND=dp), DIMENSION(n)                        :: eigenvalues, eigenvalues_single
    2566              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    2567              :       TYPE(cp_cfm_type) :: matrix_c0, matrix_c0_phase, matrix_c0_rot, matrix_gradient, &
    2568              :          matrix_gradient_phase, matrix_gradient_rot, matrix_h, matrix_output, matrix_output_phase, &
    2569              :          matrix_output_rot, matrix_s, matrix_t
    2570              :       TYPE(cp_fm_struct_type), POINTER                   :: fmstruct_nk, fmstruct_nn
    2571           32 :       TYPE(preconditioner_type) :: covariant, covariant_rot, full_all, full_all_phase, low_rank, &
    2572           28 :          low_rank_rot, preconditioner, preconditioner_rot, preconditioner_s, &
    2573           14 :          preconditioner_single, preconditioner_t
    2574              : 
    2575            2 :       NULLIFY (blacs_env, fmstruct_nk, fmstruct_nn)
    2576            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=para_env)
    2577              :       CALL cp_fm_struct_create(fmstruct=fmstruct_nn, para_env=para_env, context=blacs_env, &
    2578            2 :                                nrow_global=n, ncol_global=n)
    2579              :       CALL cp_fm_struct_create(fmstruct=fmstruct_nk, para_env=para_env, context=blacs_env, &
    2580            2 :                                nrow_global=n, ncol_global=k)
    2581            2 :       CALL cp_cfm_create(matrix_h, fmstruct_nn, name='complex preconditioner H')
    2582            2 :       CALL cp_cfm_create(matrix_s, fmstruct_nn, name='complex preconditioner S')
    2583            2 :       CALL cp_cfm_create(matrix_t, fmstruct_nn, name='complex preconditioner T')
    2584            2 :       CALL cp_cfm_create(matrix_c0, fmstruct_nk, name='complex preconditioner C')
    2585            2 :       CALL cp_cfm_create(matrix_c0_phase, fmstruct_nk, name='complex preconditioner phased C')
    2586            2 :       CALL cp_cfm_create(matrix_c0_rot, fmstruct_nk, name='complex preconditioner C U')
    2587            2 :       CALL cp_cfm_create(matrix_gradient, fmstruct_nk, name='complex preconditioner gradient')
    2588              :       CALL cp_cfm_create(matrix_gradient_phase, fmstruct_nk, &
    2589            2 :                          name='complex preconditioner phased gradient')
    2590              :       CALL cp_cfm_create(matrix_gradient_rot, fmstruct_nk, &
    2591            2 :                          name='complex preconditioner rotated gradient')
    2592            2 :       CALL cp_cfm_create(matrix_output, fmstruct_nk, name='complex preconditioner output')
    2593              :       CALL cp_cfm_create(matrix_output_phase, fmstruct_nk, &
    2594            2 :                          name='complex preconditioner phased output')
    2595              :       CALL cp_cfm_create(matrix_output_rot, fmstruct_nk, &
    2596            2 :                          name='complex preconditioner rotated output')
    2597            2 :       CALL cp_cfm_set_all(matrix_h, CMPLX(0.0_dp, 0.0_dp, KIND=dp))
    2598            2 :       CALL cp_cfm_set_all(matrix_s, CMPLX(0.0_dp, 0.0_dp, KIND=dp))
    2599            2 :       CALL cp_cfm_set_all(matrix_t, CMPLX(0.0_dp, 0.0_dp, KIND=dp))
    2600            2 :       CALL cp_cfm_set_all(matrix_c0, CMPLX(0.0_dp, 0.0_dp, KIND=dp))
    2601            2 :       CALL cp_cfm_set_all(matrix_c0_phase, CMPLX(0.0_dp, 0.0_dp, KIND=dp))
    2602            2 :       CALL cp_cfm_set_all(matrix_c0_rot, CMPLX(0.0_dp, 0.0_dp, KIND=dp))
    2603              : 
    2604              :       h(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2605            2 :       h(1, 1) = CMPLX(-0.35_dp, 0.0_dp, KIND=dp)
    2606            2 :       h(2, 2) = CMPLX(0.15_dp, 0.0_dp, KIND=dp)
    2607            2 :       h(3, 3) = CMPLX(0.90_dp, 0.0_dp, KIND=dp)
    2608            2 :       h(1, 2) = CMPLX(0.08_dp, 0.04_dp, KIND=dp)
    2609            2 :       h(2, 1) = CONJG(h(1, 2))
    2610            2 :       h(1, 3) = CMPLX(-0.06_dp, 0.03_dp, KIND=dp)
    2611            2 :       h(3, 1) = CONJG(h(1, 3))
    2612            2 :       h(2, 3) = CMPLX(0.05_dp, -0.07_dp, KIND=dp)
    2613            2 :       h(3, 2) = CONJG(h(2, 3))
    2614            2 :       s(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2615            2 :       s(1, 1) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2616            2 :       s(2, 2) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2617            2 :       s(3, 3) = CMPLX(1.4_dp, 0.0_dp, KIND=dp)
    2618            2 :       s(1, 3) = CMPLX(0.08_dp, 0.03_dp, KIND=dp)
    2619            2 :       s(3, 1) = CONJG(s(1, 3))
    2620            2 :       s(2, 3) = CMPLX(-0.04_dp, 0.02_dp, KIND=dp)
    2621            2 :       s(3, 2) = CONJG(s(2, 3))
    2622            2 :       t = h
    2623            8 :       DO i = 1, n
    2624            8 :          t(i, i) = t(i, i) + CMPLX(1.50_dp, 0.0_dp, KIND=dp)
    2625              :       END DO
    2626            2 :       c0(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2627            2 :       c0(1, 1) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2628            2 :       c0(2, 2) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2629            2 :       c0_rot(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2630            2 :       c0_rot(1, 1) = CMPLX(SQRT(0.5_dp), 0.0_dp, KIND=dp)
    2631            2 :       c0_rot(1, 2) = CMPLX(0.0_dp, SQRT(0.5_dp), KIND=dp)
    2632            2 :       c0_rot(2, 1) = CMPLX(0.0_dp, SQRT(0.5_dp), KIND=dp)
    2633            2 :       c0_rot(2, 2) = CMPLX(SQRT(0.5_dp), 0.0_dp, KIND=dp)
    2634              :       phases = [EXP(CMPLX(0.0_dp, 0.43_dp, KIND=dp)), &
    2635            2 :                 EXP(CMPLX(0.0_dp, -0.67_dp, KIND=dp))]
    2636            6 :       DO j = 1, k
    2637           18 :          c0_phase(:, j) = phases(j)*c0(:, j)
    2638              :       END DO
    2639              :       gradient(:, 1) = [CMPLX(0.17_dp, 0.02_dp, KIND=dp), &
    2640              :                         CMPLX(-0.08_dp, 0.05_dp, KIND=dp), &
    2641            8 :                         CMPLX(0.11_dp, -0.04_dp, KIND=dp)]
    2642              :       gradient(:, 2) = [CMPLX(-0.03_dp, 0.06_dp, KIND=dp), &
    2643              :                         CMPLX(0.09_dp, -0.02_dp, KIND=dp), &
    2644            8 :                         CMPLX(-0.14_dp, 0.05_dp, KIND=dp)]
    2645            6 :       DO j = 1, k
    2646           18 :          gradient_phase(:, j) = phases(j)*gradient(:, j)
    2647              :       END DO
    2648           38 :       gradient_rot = MATMUL(gradient, c0_rot(1:k, :))
    2649            6 :       c0_evals = [REAL(h(1, 1), KIND=dp), REAL(h(2, 2), KIND=dp)]
    2650              : 
    2651            8 :       DO j = 1, n
    2652           26 :          DO i = 1, n
    2653           18 :             CALL cp_cfm_set_element(matrix_h, i, j, h(i, j))
    2654           18 :             CALL cp_cfm_set_element(matrix_s, i, j, s(i, j))
    2655           24 :             CALL cp_cfm_set_element(matrix_t, i, j, t(i, j))
    2656              :          END DO
    2657              :       END DO
    2658            6 :       DO j = 1, k
    2659           18 :          DO i = 1, n
    2660           12 :             CALL cp_cfm_set_element(matrix_c0, i, j, c0(i, j))
    2661           12 :             CALL cp_cfm_set_element(matrix_c0_phase, i, j, c0_phase(i, j))
    2662           12 :             CALL cp_cfm_set_element(matrix_c0_rot, i, j, c0_rot(i, j))
    2663           12 :             CALL cp_cfm_set_element(matrix_gradient, i, j, gradient(i, j))
    2664           12 :             CALL cp_cfm_set_element(matrix_gradient_phase, i, j, gradient_phase(i, j))
    2665           16 :             CALL cp_cfm_set_element(matrix_gradient_rot, i, j, gradient_rot(i, j))
    2666              :          END DO
    2667              :       END DO
    2668              : 
    2669            2 :       CALL init_preconditioner(full_all, para_env, blacs_env)
    2670            2 :       CALL init_preconditioner(full_all_phase, para_env, blacs_env)
    2671            2 :       CALL init_preconditioner(low_rank, para_env, blacs_env)
    2672            2 :       CALL init_preconditioner(low_rank_rot, para_env, blacs_env)
    2673            2 :       CALL init_preconditioner(covariant, para_env, blacs_env)
    2674            2 :       CALL init_preconditioner(covariant_rot, para_env, blacs_env)
    2675            2 :       CALL init_preconditioner(preconditioner, para_env, blacs_env)
    2676            2 :       CALL init_preconditioner(preconditioner_rot, para_env, blacs_env)
    2677            2 :       CALL init_preconditioner(preconditioner_s, para_env, blacs_env)
    2678            2 :       CALL init_preconditioner(preconditioner_single, para_env, blacs_env)
    2679            2 :       CALL init_preconditioner(preconditioner_t, para_env, blacs_env)
    2680            2 :       CALL make_complex_full_single_inverse(preconditioner, matrix_c0, matrix_h, matrix_s, 0.20_dp)
    2681            2 :       CALL make_complex_full_single_inverse(preconditioner_rot, matrix_c0_rot, matrix_h, matrix_s, 0.20_dp)
    2682            2 :       CALL make_complex_full_s_inverse(preconditioner_s, matrix_s)
    2683            2 :       CALL make_complex_full_single(preconditioner_single, matrix_h, matrix_s, 0.15_dp, 0.20_dp)
    2684            2 :       CALL make_complex_full_kinetic(preconditioner_t, matrix_t, matrix_s, 0.20_dp)
    2685            2 :       CALL make_complex_full_all(full_all, matrix_c0, matrix_h, matrix_s, c0_evals, 0.08_dp)
    2686              :       CALL make_complex_full_all(full_all_phase, matrix_c0_phase, matrix_h, matrix_s, &
    2687            2 :                                  c0_evals, 0.08_dp)
    2688            2 :       CALL apply_preconditioner(full_all, matrix_gradient, matrix_output)
    2689            2 :       CALL apply_preconditioner(full_all_phase, matrix_gradient_phase, matrix_output_phase)
    2690            8 :       DO j = 1, n
    2691           26 :          DO i = 1, n
    2692           18 :             CALL cp_cfm_get_element(preconditioner%complex_fm, i, j, value)
    2693           18 :             inverse(i, j) = value
    2694           18 :             CALL cp_cfm_get_element(preconditioner_rot%complex_fm, i, j, value)
    2695           18 :             inverse_rot(i, j) = value
    2696           18 :             CALL cp_cfm_get_element(preconditioner_s%complex_fm, i, j, value)
    2697           18 :             inverse_s(i, j) = value
    2698           18 :             CALL cp_cfm_get_element(preconditioner_single%complex_fm, i, j, value)
    2699           18 :             inverse_single(i, j) = value
    2700           18 :             CALL cp_cfm_get_element(preconditioner_t%complex_fm, i, j, value)
    2701           24 :             inverse_t(i, j) = value
    2702              :          END DO
    2703              :       END DO
    2704           26 :       gauge_error = MAXVAL(ABS(inverse - inverse_rot))
    2705           26 :       hermitian_error = MAXVAL(ABS(inverse - CONJG(TRANSPOSE(inverse))))
    2706            2 :       CALL diag_complex(inverse, eigenvectors, eigenvalues)
    2707           26 :       full_single_hermitian_error = MAXVAL(ABS(inverse_single - CONJG(TRANSPOSE(inverse_single))))
    2708            2 :       CALL diag_complex(inverse_single, eigenvectors, eigenvalues_single)
    2709            2 :       CALL dense_hermitian_inverse(s, reference_s)
    2710           26 :       CALL dense_hermitian_inverse(t + 0.20_dp*s, reference_t)
    2711           26 :       s_error = MAXVAL(ABS(inverse_s - reference_s))
    2712           26 :       t_error = MAXVAL(ABS(inverse_t - reference_t))
    2713            6 :       DO j = 1, k
    2714           18 :          DO i = 1, n
    2715           12 :             CALL cp_cfm_get_element(matrix_output, i, j, value)
    2716           12 :             output(i, j) = value
    2717           12 :             CALL cp_cfm_get_element(matrix_output_phase, i, j, value)
    2718           16 :             output_phase(i, j) = value
    2719              :          END DO
    2720              :       END DO
    2721            8 :       DO j = 1, n
    2722           26 :          DO i = 1, n
    2723           18 :             CALL cp_cfm_get_element(full_all%complex_fm, i, j, value)
    2724           24 :             full_all_vectors(i, j) = value
    2725              :          END DO
    2726              :       END DO
    2727            2 :       full_all_gauge_error = 0.0_dp
    2728            6 :       DO j = 1, k
    2729              :          full_all_gauge_error = MAX(full_all_gauge_error, &
    2730           18 :                                     MAXVAL(ABS(output_phase(:, j) - phases(j)*output(:, j))))
    2731              :       END DO
    2732              :       spectral_metric = MATMUL(CONJG(TRANSPOSE(full_all_vectors)), &
    2733          158 :                                MATMUL(s, full_all_vectors))
    2734              :       full_all_orthogonality_error = MAXVAL(ABS(spectral_metric - &
    2735              :                                                 RESHAPE([CMPLX(1.0_dp, 0.0_dp, KIND=dp), &
    2736              :                                                          CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2737              :                                                          CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2738              :                                                          CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2739              :                                                          CMPLX(1.0_dp, 0.0_dp, KIND=dp), &
    2740              :                                                          CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2741              :                                                          CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2742              :                                                          CMPLX(0.0_dp, 0.0_dp, KIND=dp), &
    2743           26 :                                                          CMPLX(1.0_dp, 0.0_dp, KIND=dp)], [n, n])))
    2744           18 :       full_all_descent = REAL(SUM(CONJG(gradient)*output), KIND=dp)
    2745              : 
    2746              :       CALL make_complex_fermi_low_rank(low_rank, matrix_c0, matrix_h, matrix_s, &
    2747            2 :                                        0.08_dp, 1, 1.0_dp, 1.0E-8_dp)
    2748              :       CALL make_complex_fermi_low_rank(low_rank_rot, matrix_c0_rot, matrix_h, matrix_s, &
    2749            2 :                                        0.08_dp, 1, 1.0_dp, 1.0E-8_dp)
    2750            2 :       CALL apply_preconditioner(low_rank, matrix_gradient, matrix_output)
    2751            2 :       CALL apply_preconditioner(low_rank_rot, matrix_gradient_rot, matrix_output_rot)
    2752            6 :       DO j = 1, k
    2753           18 :          DO i = 1, n
    2754           12 :             CALL cp_cfm_get_element(matrix_output, i, j, value)
    2755           12 :             output(i, j) = value
    2756           12 :             CALL cp_cfm_get_element(matrix_output_rot, i, j, value)
    2757           16 :             output_rot(i, j) = value
    2758              :          END DO
    2759              :       END DO
    2760           54 :       low_rank_gauge_error = MAXVAL(ABS(output_rot - MATMUL(output, c0_rot(1:k, :))))
    2761           18 :       low_rank_descent = REAL(SUM(CONJG(gradient)*output), KIND=dp)
    2762              : 
    2763            2 :       CALL make_complex_full_all_covariant(covariant, matrix_c0, matrix_h, matrix_s, 0.08_dp)
    2764            2 :       CALL make_complex_full_all_covariant(covariant_rot, matrix_c0_rot, matrix_h, matrix_s, 0.08_dp)
    2765            2 :       CALL apply_preconditioner(covariant, matrix_gradient, matrix_output)
    2766            2 :       CALL apply_preconditioner(covariant_rot, matrix_gradient_rot, matrix_output_rot)
    2767            6 :       DO j = 1, k
    2768           18 :          DO i = 1, n
    2769           12 :             CALL cp_cfm_get_element(matrix_output, i, j, value)
    2770           12 :             output(i, j) = value
    2771           12 :             CALL cp_cfm_get_element(matrix_output_rot, i, j, value)
    2772           16 :             output_rot(i, j) = value
    2773              :          END DO
    2774              :       END DO
    2775           54 :       covariant_gauge_error = MAXVAL(ABS(output_rot - MATMUL(output, c0_rot(1:k, :))))
    2776           18 :       covariant_descent = REAL(SUM(CONJG(gradient)*output), KIND=dp)
    2777            8 :       IF (gauge_error > 1.0E-11_dp .OR. hermitian_error > 1.0E-12_dp .OR. &
    2778            0 :           MINVAL(eigenvalues) <= 0.0_dp) nfail = nfail + 1
    2779            2 :       IF (s_error > 1.0E-11_dp .OR. t_error > 1.0E-11_dp) nfail = nfail + 1
    2780            8 :       IF (full_single_hermitian_error > 1.0E-12_dp .OR. &
    2781            0 :           MINVAL(eigenvalues_single) <= 0.0_dp) nfail = nfail + 1
    2782              :       IF (full_all_gauge_error > 1.0E-11_dp .OR. &
    2783            2 :           full_all_orthogonality_error > 1.0E-11_dp .OR. full_all_descent <= 0.0_dp) nfail = nfail + 1
    2784            2 :       IF (low_rank_gauge_error > 1.0E-11_dp .OR. low_rank_descent <= 0.0_dp) nfail = nfail + 1
    2785            2 :       IF (covariant_gauge_error > 1.0E-11_dp .OR. covariant_descent <= 0.0_dp) nfail = nfail + 1
    2786            2 :       IF (para_env%is_source()) THEN
    2787            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex FULL_SINGLE gauge/hermitian/min-eigenvalue', &
    2788            5 :             gauge_error, hermitian_error, MINVAL(eigenvalues)
    2789            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') 'complex S/KINETIC inverse errors', s_error, t_error
    2790            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') 'complex FULL_SINGLE hermitian/min-eigenvalue', &
    2791            5 :             full_single_hermitian_error, MINVAL(eigenvalues_single)
    2792            1 :          WRITE (io_unit, '(A,3(1X,ES13.6))') 'complex FULL_ALL phase/orthogonality/descent', &
    2793            2 :             full_all_gauge_error, full_all_orthogonality_error, full_all_descent
    2794            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') 'complex FERMI_LOW_RANK covariance/descent', &
    2795            2 :             low_rank_gauge_error, low_rank_descent
    2796            1 :          WRITE (io_unit, '(A,2(1X,ES13.6))') 'complex FULL_ALL_COVARIANT covariance/descent', &
    2797            2 :             covariant_gauge_error, covariant_descent
    2798              :       END IF
    2799              : 
    2800            2 :       CALL destroy_preconditioner(covariant_rot)
    2801            2 :       CALL destroy_preconditioner(covariant)
    2802            2 :       CALL destroy_preconditioner(low_rank_rot)
    2803            2 :       CALL destroy_preconditioner(low_rank)
    2804            2 :       CALL destroy_preconditioner(full_all_phase)
    2805            2 :       CALL destroy_preconditioner(full_all)
    2806            2 :       CALL destroy_preconditioner(preconditioner_t)
    2807            2 :       CALL destroy_preconditioner(preconditioner_single)
    2808            2 :       CALL destroy_preconditioner(preconditioner_s)
    2809            2 :       CALL destroy_preconditioner(preconditioner_rot)
    2810            2 :       CALL destroy_preconditioner(preconditioner)
    2811            2 :       CALL cp_cfm_release(matrix_c0_rot)
    2812            2 :       CALL cp_cfm_release(matrix_c0_phase)
    2813            2 :       CALL cp_cfm_release(matrix_c0)
    2814            2 :       CALL cp_cfm_release(matrix_output_phase)
    2815            2 :       CALL cp_cfm_release(matrix_output)
    2816            2 :       CALL cp_cfm_release(matrix_output_rot)
    2817            2 :       CALL cp_cfm_release(matrix_gradient_rot)
    2818            2 :       CALL cp_cfm_release(matrix_gradient_phase)
    2819            2 :       CALL cp_cfm_release(matrix_gradient)
    2820            2 :       CALL cp_cfm_release(matrix_t)
    2821            2 :       CALL cp_cfm_release(matrix_s)
    2822            2 :       CALL cp_cfm_release(matrix_h)
    2823            2 :       CALL cp_fm_struct_release(fmstruct_nk)
    2824            2 :       CALL cp_fm_struct_release(fmstruct_nn)
    2825            2 :       CALL cp_blacs_env_release(blacs_env)
    2826              : 
    2827            6 :    END SUBROUTINE test_complex_preconditioner_gauge
    2828              : 
    2829              : ! **************************************************************************************************
    2830              : !> \brief Check complex covariant preconditioning within equal-occupation ROKS blocks.
    2831              : !> \param para_env parallel environment
    2832              : !> \param nfail accumulated number of failures
    2833              : ! **************************************************************************************************
    2834            2 :    SUBROUTINE test_complex_occupation_block_preconditioner(para_env, nfail)
    2835              :       TYPE(mp_para_env_type), POINTER                    :: para_env
    2836              :       INTEGER, INTENT(INOUT)                             :: nfail
    2837              : 
    2838              :       INTEGER, PARAMETER                                 :: nbas = 4, norb = 3
    2839              : 
    2840              :       COMPLEX(KIND=dp)                                   :: value
    2841              :       COMPLEX(KIND=dp), DIMENSION(nbas, nbas)            :: hamiltonian, overlap
    2842              :       COMPLEX(KIND=dp), DIMENSION(nbas, norb)            :: coeff, coeff_rot, gradient, &
    2843              :                                                             gradient_rot, output, output_rot
    2844              :       COMPLEX(KIND=dp), DIMENSION(norb, norb)            :: block_rotation, private_rotation
    2845              :       INTEGER                                            :: i, j
    2846              :       REAL(KIND=dp)                                      :: covariance_error, cross_block_error, &
    2847              :                                                             descent, shifted_eigenvalue_error
    2848              :       REAL(KIND=dp), DIMENSION(norb, 2)                  :: occupation_signature
    2849              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env
    2850              :       TYPE(cp_cfm_type) :: matrix_c, matrix_c_rot, matrix_gradient, matrix_gradient_rot, matrix_h, &
    2851              :          matrix_output, matrix_output_rot, matrix_s
    2852              :       TYPE(cp_fm_struct_type), POINTER                   :: fmstruct_nk, fmstruct_nn
    2853           14 :       TYPE(preconditioner_type)                          :: preconditioner, preconditioner_rot
    2854              : 
    2855            2 :       NULLIFY (blacs_env, fmstruct_nk, fmstruct_nn)
    2856            2 :       CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=para_env)
    2857              :       CALL cp_fm_struct_create(fmstruct=fmstruct_nn, para_env=para_env, context=blacs_env, &
    2858            2 :                                nrow_global=nbas, ncol_global=nbas)
    2859              :       CALL cp_fm_struct_create(fmstruct=fmstruct_nk, para_env=para_env, context=blacs_env, &
    2860            2 :                                nrow_global=nbas, ncol_global=norb)
    2861            2 :       CALL cp_cfm_create(matrix_h, fmstruct_nn, name='occupation-block H')
    2862            2 :       CALL cp_cfm_create(matrix_s, fmstruct_nn, name='occupation-block S')
    2863            2 :       CALL cp_cfm_create(matrix_c, fmstruct_nk, name='occupation-block C')
    2864            2 :       CALL cp_cfm_create(matrix_c_rot, fmstruct_nk, name='occupation-block C U')
    2865            2 :       CALL cp_cfm_create(matrix_gradient, fmstruct_nk, name='occupation-block gradient')
    2866              :       CALL cp_cfm_create(matrix_gradient_rot, fmstruct_nk, &
    2867            2 :                          name='occupation-block rotated gradient')
    2868            2 :       CALL cp_cfm_create(matrix_output, fmstruct_nk, name='occupation-block output')
    2869            2 :       CALL cp_cfm_create(matrix_output_rot, fmstruct_nk, name='occupation-block rotated output')
    2870              : 
    2871              :       hamiltonian(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2872            2 :       hamiltonian(1, 1) = CMPLX(-0.42_dp, 0.0_dp, KIND=dp)
    2873            2 :       hamiltonian(2, 2) = CMPLX(-0.18_dp, 0.0_dp, KIND=dp)
    2874            2 :       hamiltonian(3, 3) = CMPLX(0.07_dp, 0.0_dp, KIND=dp)
    2875            2 :       hamiltonian(4, 4) = CMPLX(0.83_dp, 0.0_dp, KIND=dp)
    2876            2 :       hamiltonian(1, 2) = CMPLX(0.09_dp, 0.04_dp, KIND=dp)
    2877            2 :       hamiltonian(2, 1) = CONJG(hamiltonian(1, 2))
    2878            2 :       hamiltonian(1, 3) = CMPLX(-0.06_dp, 0.03_dp, KIND=dp)
    2879            2 :       hamiltonian(3, 1) = CONJG(hamiltonian(1, 3))
    2880            2 :       hamiltonian(2, 3) = CMPLX(0.05_dp, -0.02_dp, KIND=dp)
    2881            2 :       hamiltonian(3, 2) = CONJG(hamiltonian(2, 3))
    2882            2 :       hamiltonian(1, 4) = CMPLX(0.04_dp, -0.03_dp, KIND=dp)
    2883            2 :       hamiltonian(4, 1) = CONJG(hamiltonian(1, 4))
    2884            2 :       hamiltonian(2, 4) = CMPLX(-0.02_dp, 0.05_dp, KIND=dp)
    2885            2 :       hamiltonian(4, 2) = CONJG(hamiltonian(2, 4))
    2886            2 :       hamiltonian(3, 4) = CMPLX(0.07_dp, 0.01_dp, KIND=dp)
    2887            2 :       hamiltonian(4, 3) = CONJG(hamiltonian(3, 4))
    2888            2 :       overlap(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2889           10 :       DO i = 1, nbas
    2890           10 :          overlap(i, i) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2891              :       END DO
    2892            2 :       coeff(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2893            8 :       DO i = 1, norb
    2894            8 :          coeff(i, i) = CMPLX(1.0_dp, 0.0_dp, KIND=dp)
    2895              :       END DO
    2896            2 :       block_rotation(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    2897            2 :       block_rotation(1, 1) = CMPLX(SQRT(0.7_dp), 0.0_dp, KIND=dp)
    2898            2 :       block_rotation(1, 3) = CMPLX(0.0_dp, SQRT(0.3_dp), KIND=dp)
    2899            2 :       block_rotation(2, 2) = EXP(CMPLX(0.0_dp, 0.37_dp, KIND=dp))
    2900            2 :       block_rotation(3, 1) = CMPLX(0.0_dp, SQRT(0.3_dp), KIND=dp)
    2901            2 :       block_rotation(3, 3) = CMPLX(SQRT(0.7_dp), 0.0_dp, KIND=dp)
    2902           98 :       coeff_rot = MATMUL(coeff, block_rotation)
    2903              :       gradient(:, 1) = [CMPLX(0.12_dp, 0.03_dp, KIND=dp), &
    2904              :                         CMPLX(-0.04_dp, 0.07_dp, KIND=dp), &
    2905              :                         CMPLX(0.08_dp, -0.02_dp, KIND=dp), &
    2906           10 :                         CMPLX(-0.05_dp, 0.06_dp, KIND=dp)]
    2907              :       gradient(:, 2) = [CMPLX(-0.06_dp, 0.02_dp, KIND=dp), &
    2908              :                         CMPLX(0.11_dp, -0.05_dp, KIND=dp), &
    2909              :                         CMPLX(0.03_dp, 0.09_dp, KIND=dp), &
    2910           10 :                         CMPLX(0.07_dp, -0.01_dp, KIND=dp)]
    2911              :       gradient(:, 3) = [CMPLX(0.04_dp, -0.08_dp, KIND=dp), &
    2912              :                         CMPLX(-0.02_dp, 0.05_dp, KIND=dp), &
    2913              :                         CMPLX(0.10_dp, 0.01_dp, KIND=dp), &
    2914           10 :                         CMPLX(-0.03_dp, -0.06_dp, KIND=dp)]
    2915           98 :       gradient_rot = MATMUL(gradient, block_rotation)
    2916            8 :       occupation_signature(:, 1) = [1.0_dp, 1.0_dp, 1.0_dp]
    2917            8 :       occupation_signature(:, 2) = [1.0_dp, 0.0_dp, 1.0_dp]
    2918              : 
    2919           10 :       DO j = 1, nbas
    2920           42 :          DO i = 1, nbas
    2921           32 :             CALL cp_cfm_set_element(matrix_h, i, j, hamiltonian(i, j))
    2922           40 :             CALL cp_cfm_set_element(matrix_s, i, j, overlap(i, j))
    2923              :          END DO
    2924              :       END DO
    2925            8 :       DO j = 1, norb
    2926           32 :          DO i = 1, nbas
    2927           24 :             CALL cp_cfm_set_element(matrix_c, i, j, coeff(i, j))
    2928           24 :             CALL cp_cfm_set_element(matrix_c_rot, i, j, coeff_rot(i, j))
    2929           24 :             CALL cp_cfm_set_element(matrix_gradient, i, j, gradient(i, j))
    2930           30 :             CALL cp_cfm_set_element(matrix_gradient_rot, i, j, gradient_rot(i, j))
    2931              :          END DO
    2932              :       END DO
    2933              : 
    2934            2 :       CALL init_preconditioner(preconditioner, para_env, blacs_env)
    2935            2 :       CALL init_preconditioner(preconditioner_rot, para_env, blacs_env)
    2936              :       CALL make_complex_full_all_covariant(preconditioner, matrix_c, matrix_h, matrix_s, &
    2937            2 :                                            0.08_dp, occupation_signature)
    2938              :       CALL make_complex_full_all_covariant(preconditioner_rot, matrix_c_rot, matrix_h, matrix_s, &
    2939            2 :                                            0.08_dp, occupation_signature)
    2940            2 :       CALL apply_preconditioner(preconditioner, matrix_gradient, matrix_output)
    2941            2 :       CALL apply_preconditioner(preconditioner_rot, matrix_gradient_rot, matrix_output_rot)
    2942            8 :       DO j = 1, norb
    2943           32 :          DO i = 1, nbas
    2944           24 :             CALL cp_cfm_get_element(matrix_output, i, j, value)
    2945           24 :             output(i, j) = value
    2946           24 :             CALL cp_cfm_get_element(matrix_output_rot, i, j, value)
    2947           30 :             output_rot(i, j) = value
    2948              :          END DO
    2949              :       END DO
    2950            8 :       DO j = 1, norb
    2951           26 :          DO i = 1, norb
    2952           18 :             CALL cp_cfm_get_element(preconditioner%occ_rotation_complex, i, j, value)
    2953           24 :             private_rotation(i, j) = value
    2954              :          END DO
    2955              :       END DO
    2956              : 
    2957          128 :       covariance_error = MAXVAL(ABS(output_rot - MATMUL(output, block_rotation)))
    2958              :       cross_block_error = MAX(MAXVAL(ABS(private_rotation([1, 3], 3))), &
    2959           10 :                               MAXVAL(ABS(private_rotation(2, 1:2))))
    2960            8 :       shifted_eigenvalue_error = MAXVAL(ABS(preconditioner%occ_evals))
    2961           32 :       descent = REAL(SUM(CONJG(gradient)*output), KIND=dp)
    2962              :       IF (covariance_error > 1.0E-11_dp .OR. cross_block_error > 1.0E-12_dp .OR. &
    2963            2 :           shifted_eigenvalue_error > 1.0_dp .OR. descent <= 0.0_dp) nfail = nfail + 1
    2964            2 :       IF (para_env%is_source()) THEN
    2965              :          WRITE (io_unit, '(A,4(1X,ES13.6))') &
    2966            1 :             'complex occupation-block covariance/cross-block/max-eigenvalue/descent', &
    2967            2 :             covariance_error, cross_block_error, shifted_eigenvalue_error, descent
    2968              :       END IF
    2969              : 
    2970            2 :       CALL destroy_preconditioner(preconditioner_rot)
    2971            2 :       CALL destroy_preconditioner(preconditioner)
    2972            2 :       CALL cp_cfm_release(matrix_output_rot)
    2973            2 :       CALL cp_cfm_release(matrix_output)
    2974            2 :       CALL cp_cfm_release(matrix_gradient_rot)
    2975            2 :       CALL cp_cfm_release(matrix_gradient)
    2976            2 :       CALL cp_cfm_release(matrix_c_rot)
    2977            2 :       CALL cp_cfm_release(matrix_c)
    2978            2 :       CALL cp_cfm_release(matrix_s)
    2979            2 :       CALL cp_cfm_release(matrix_h)
    2980            2 :       CALL cp_fm_struct_release(fmstruct_nk)
    2981            2 :       CALL cp_fm_struct_release(fmstruct_nn)
    2982            2 :       CALL cp_blacs_env_release(blacs_env)
    2983              : 
    2984            6 :    END SUBROUTINE test_complex_occupation_block_preconditioner
    2985              : 
    2986              : ! **************************************************************************************************
    2987              : !> \brief Check the intentionally narrow complex k-point preconditioner support matrix.
    2988              : !> \param nfail accumulated number of failures
    2989              : ! **************************************************************************************************
    2990            2 :    SUBROUTINE test_preconditioner_support(nfail)
    2991              :       INTEGER, INTENT(INOUT)                             :: nfail
    2992              : 
    2993            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_none, .FALSE.)) nfail = nfail + 1
    2994            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_fermi_low_rank, .FALSE.)) nfail = nfail + 1
    2995            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_full_single_inverse, .FALSE.)) THEN
    2996            0 :          nfail = nfail + 1
    2997              :       END IF
    2998            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_full_all, .FALSE.)) nfail = nfail + 1
    2999            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_full_single, .FALSE.)) nfail = nfail + 1
    3000            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported( &
    3001            0 :           ot_precond_full_all_covariant, .FALSE.)) nfail = nfail + 1
    3002            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_full_kinetic, .FALSE.)) nfail = nfail + 1
    3003            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_supported(ot_precond_s_inverse, .FALSE.)) nfail = nfail + 1
    3004            2 :       IF (qs_ot_kpoint_preconditioner_supported(ot_precond_full_single_inverse, .TRUE.)) nfail = nfail + 1
    3005              : 
    3006            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3007            0 :           ot_precond_full_single, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3008            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3009            0 :           ot_precond_full_single, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3010              : 
    3011            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3012            0 :           ot_precond_full_single_inverse, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3013            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3014            0 :           ot_precond_full_single_inverse, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3015            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3016            0 :           ot_precond_full_single_inverse, ot_precond_solver_direct, .FALSE.)) nfail = nfail + 1
    3017            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3018            0 :           ot_precond_full_single_inverse, ot_precond_solver_chebyshev, .FALSE.)) nfail = nfail + 1
    3019            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3020            0 :           ot_precond_full_all, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3021            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3022            0 :           ot_precond_full_all, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3023            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3024            0 :           ot_precond_fermi_low_rank, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3025            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3026            0 :           ot_precond_fermi_low_rank, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3027            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3028            0 :           ot_precond_full_all_covariant, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3029            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3030            0 :           ot_precond_full_all_covariant, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3031            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3032            0 :           ot_precond_full_kinetic, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3033            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3034            0 :           ot_precond_full_kinetic, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3035            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3036            0 :           ot_precond_full_kinetic, ot_precond_solver_direct, .FALSE.)) nfail = nfail + 1
    3037            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3038            0 :           ot_precond_s_inverse, ot_precond_solver_default, .FALSE.)) nfail = nfail + 1
    3039            2 :       IF (.NOT. qs_ot_kpoint_preconditioner_solver_supported( &
    3040            0 :           ot_precond_s_inverse, ot_precond_solver_inv_chol, .FALSE.)) nfail = nfail + 1
    3041            2 :       IF (qs_ot_kpoint_preconditioner_solver_supported( &
    3042            0 :           ot_precond_s_inverse, ot_precond_solver_chebyshev, .FALSE.)) nfail = nfail + 1
    3043            2 :       IF (ABS(qs_ot_kpoint_preconditioner_scale(0.25_dp) - 4.0_dp) > EPSILON(1.0_dp)) THEN
    3044            0 :          nfail = nfail + 1
    3045              :       END IF
    3046              : 
    3047            2 :    END SUBROUTINE test_preconditioner_support
    3048              : 
    3049              : ! **************************************************************************************************
    3050              : !> \brief Create and reserve a one-block DBCSR matrix.
    3051              : !> \param matrix ...
    3052              : !> \param name ...
    3053              : !> \param dist ...
    3054              : !> \param row_size ...
    3055              : !> \param col_size ...
    3056              : ! **************************************************************************************************
    3057          210 :    SUBROUTINE create_one_block(matrix, name, dist, row_size, col_size)
    3058              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix
    3059              :       CHARACTER(LEN=*), INTENT(IN)                       :: name
    3060              :       TYPE(dbcsr_distribution_type), INTENT(IN)          :: dist
    3061              :       INTEGER, DIMENSION(:), INTENT(INOUT), POINTER      :: row_size, col_size
    3062              : 
    3063          210 :       CALL dbcsr_create(matrix, name, dist, dbcsr_type_no_symmetry, row_size, col_size)
    3064          210 :       CALL dbcsr_reserve_blocks(matrix, rows=[1], cols=[1])
    3065          210 :       CALL dbcsr_finalize(matrix)
    3066              : 
    3067          210 :    END SUBROUTINE create_one_block
    3068              : 
    3069              : ! **************************************************************************************************
    3070              : !> \brief Store a dense complex matrix in a real/imaginary DBCSR pair.
    3071              : !> \param matrix_re ...
    3072              : !> \param matrix_im ...
    3073              : !> \param matrix ...
    3074              : ! **************************************************************************************************
    3075           28 :    SUBROUTINE put_complex_pair(matrix_re, matrix_im, matrix)
    3076              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix_re, matrix_im
    3077              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: matrix
    3078              : 
    3079          248 :       CALL dbcsr_put_block(matrix_re, 1, 1, REAL(matrix, KIND=dp))
    3080          248 :       CALL dbcsr_put_block(matrix_im, 1, 1, AIMAG(matrix))
    3081              : 
    3082           28 :    END SUBROUTINE put_complex_pair
    3083              : 
    3084              : ! **************************************************************************************************
    3085              : !> \brief Retrieve a dense complex matrix from a one-block DBCSR pair.
    3086              : !> \param matrix_re ...
    3087              : !> \param matrix_im ...
    3088              : !> \param matrix ...
    3089              : ! **************************************************************************************************
    3090            8 :    SUBROUTINE get_complex_pair(matrix_re, matrix_im, matrix)
    3091              :       TYPE(dbcsr_type), INTENT(INOUT)                    :: matrix_re, matrix_im
    3092              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: matrix
    3093              : 
    3094              :       LOGICAL                                            :: found
    3095            8 :       REAL(KIND=dp), DIMENSION(:, :), POINTER            :: block
    3096              : 
    3097           64 :       matrix(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    3098            8 :       CALL dbcsr_get_block_p(matrix_re, 1, 1, block, found)
    3099           64 :       IF (found) matrix = matrix + CMPLX(block, 0.0_dp, KIND=dp)
    3100            8 :       CALL dbcsr_get_block_p(matrix_im, 1, 1, block, found)
    3101           64 :       IF (found) matrix = matrix + CMPLX(0.0_dp, block, KIND=dp)
    3102              : 
    3103            8 :    END SUBROUTINE get_complex_pair
    3104              : 
    3105              : ! **************************************************************************************************
    3106              : !> \brief Dense fixed-occupation energy after finite REF orthogonalization and rotation.
    3107              : !> \param x REF coordinate
    3108              : !> \param generator anti-Hermitian rotation generator
    3109              : !> \param hamiltonian Hermitian one-particle Hamiltonian
    3110              : !> \param occupation orbital occupations
    3111              : !> \return energy
    3112              : ! **************************************************************************************************
    3113            8 :    FUNCTION rotated_polar_energy(x, generator, hamiltonian, occupation) RESULT(energy)
    3114              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: x, generator, hamiltonian
    3115              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation
    3116              :       REAL(KIND=dp)                                      :: energy
    3117              : 
    3118              :       COMPLEX(KIND=dp), &
    3119           16 :          DIMENSION(SIZE(x, 1), SIZE(x, 2))               :: orbitals
    3120              :       COMPLEX(KIND=dp), &
    3121           16 :          DIMENSION(SIZE(x, 2), SIZE(x, 2))               :: inverse_sqrt, rotation
    3122              :       INTEGER                                            :: i
    3123              : 
    3124          176 :       CALL dense_inverse_sqrt(MATMUL(CONJG(TRANSPOSE(x)), x), inverse_sqrt)
    3125            8 :       rotation = dense_antihermitian_exp(generator)
    3126          496 :       orbitals = MATMUL(MATMUL(x, inverse_sqrt), rotation)
    3127            8 :       energy = 0.0_dp
    3128           24 :       DO i = 1, SIZE(occupation)
    3129              :          energy = energy + occupation(i)* &
    3130          296 :                   REAL(DOT_PRODUCT(orbitals(:, i), MATMUL(hamiltonian, orbitals(:, i))), KIND=dp)
    3131              :       END DO
    3132              : 
    3133            8 :    END FUNCTION rotated_polar_energy
    3134              : 
    3135              : ! **************************************************************************************************
    3136              : !> \brief Dense reference exponential for an anti-Hermitian matrix.
    3137              : !> \param generator anti-Hermitian generator
    3138              : !> \return its unitary exponential
    3139              : ! **************************************************************************************************
    3140           52 :    FUNCTION dense_antihermitian_exp(generator) RESULT(rotation)
    3141              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: generator
    3142              :       COMPLEX(KIND=dp), DIMENSION(SIZE(generator, 1), &
    3143              :          SIZE(generator, 2))                             :: rotation
    3144              : 
    3145              :       COMPLEX(KIND=dp), DIMENSION(SIZE(generator, 1), &
    3146           52 :          SIZE(generator, 2))                             :: hermitian, vectors
    3147              :       INTEGER                                            :: i
    3148           52 :       REAL(KIND=dp), DIMENSION(SIZE(generator, 1))       :: eigenvalues
    3149              : 
    3150          556 :       hermitian = CMPLX(0.0_dp, 1.0_dp, KIND=dp)*generator
    3151           52 :       CALL diag_complex(hermitian, vectors, eigenvalues)
    3152          556 :       rotation(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    3153          188 :       DO i = 1, SIZE(eigenvalues)
    3154              :          rotation = rotation + EXP(CMPLX(0.0_dp, -eigenvalues(i), KIND=dp))* &
    3155          544 :                     MATMUL(RESHAPE(vectors(:, i), [SIZE(generator, 1), 1]), &
    3156         4388 :                            RESHAPE(CONJG(vectors(:, i)), [1, SIZE(generator, 1)]))
    3157              :       END DO
    3158              : 
    3159           52 :    END FUNCTION dense_antihermitian_exp
    3160              : 
    3161              : ! **************************************************************************************************
    3162              : !> \brief Hermitian inverse for an independent dense reference.
    3163              : !> \param matrix ...
    3164              : !> \param inverse ...
    3165              : ! **************************************************************************************************
    3166            4 :    SUBROUTINE dense_hermitian_inverse(matrix, inverse)
    3167              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: matrix
    3168              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: inverse
    3169              : 
    3170              :       COMPLEX(KIND=dp), &
    3171            8 :          DIMENSION(SIZE(matrix, 1), SIZE(matrix, 2))     :: vectors
    3172              :       INTEGER                                            :: i
    3173            8 :       REAL(KIND=dp), DIMENSION(SIZE(matrix, 1))          :: eigenvalues
    3174              : 
    3175            4 :       CALL diag_complex(matrix, vectors, eigenvalues)
    3176           52 :       inverse(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    3177           16 :       DO i = 1, SIZE(eigenvalues)
    3178           48 :          inverse = inverse + MATMUL(RESHAPE(vectors(:, i), [SIZE(matrix, 1), 1]), &
    3179              :                                     RESHAPE(CONJG(vectors(:, i)), [1, SIZE(matrix, 1)]))/ &
    3180          436 :                    eigenvalues(i)
    3181              :       END DO
    3182              : 
    3183            4 :    END SUBROUTINE dense_hermitian_inverse
    3184              : 
    3185              : ! **************************************************************************************************
    3186              : !> \brief Hermitian inverse square root for the independent dense reference.
    3187              : !> \param matrix ...
    3188              : !> \param inverse ...
    3189              : ! **************************************************************************************************
    3190           18 :    SUBROUTINE dense_inverse_sqrt(matrix, inverse)
    3191              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: matrix
    3192              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: inverse
    3193              : 
    3194              :       COMPLEX(KIND=dp), &
    3195           36 :          DIMENSION(SIZE(matrix, 1), SIZE(matrix, 2))     :: vectors
    3196              :       INTEGER                                            :: i
    3197           36 :       REAL(KIND=dp), DIMENSION(SIZE(matrix, 1))          :: eigenvalues
    3198              : 
    3199           18 :       CALL diag_complex(matrix, vectors, eigenvalues)
    3200          126 :       inverse(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    3201           54 :       DO i = 1, SIZE(eigenvalues)
    3202          144 :          inverse = inverse + MATMUL(RESHAPE(vectors(:, i), [SIZE(matrix, 1), 1]), &
    3203              :                                     RESHAPE(CONJG(vectors(:, i)), [1, SIZE(matrix, 1)]))/ &
    3204          810 :                    SQRT(eigenvalues(i))
    3205              :       END DO
    3206              : 
    3207           18 :    END SUBROUTINE dense_inverse_sqrt
    3208              : 
    3209              : ! **************************************************************************************************
    3210              : !> \brief Energy of the polar-orthonormalized occupied subspace.
    3211              : !> \param coordinate ...
    3212              : !> \param hamiltonian ...
    3213              : !> \return ...
    3214              : ! **************************************************************************************************
    3215            4 :    FUNCTION polar_energy(coordinate, hamiltonian) RESULT(energy)
    3216              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: coordinate, hamiltonian
    3217              :       REAL(KIND=dp)                                      :: energy
    3218              : 
    3219              :       COMPLEX(KIND=dp), DIMENSION(SIZE(coordinate, 1), &
    3220            8 :          SIZE(coordinate, 2))                            :: orbitals
    3221              :       COMPLEX(KIND=dp), DIMENSION(SIZE(coordinate, 2), &
    3222            8 :          SIZE(coordinate, 2))                            :: inverse
    3223              : 
    3224           88 :       CALL dense_inverse_sqrt(MATMUL(CONJG(TRANSPOSE(coordinate)), coordinate), inverse)
    3225          116 :       orbitals = MATMUL(coordinate, inverse)
    3226          156 :       energy = REAL(SUM(CONJG(orbitals)*MATMUL(hamiltonian, orbitals)), KIND=dp)
    3227              : 
    3228            4 :    END FUNCTION polar_energy
    3229              : 
    3230              : ! **************************************************************************************************
    3231              : !> \brief Independent dense finite STRICT transformation for a Hermitian overlap.
    3232              : !> \param c0 reference orbitals
    3233              : !> \param coordinate tangent coordinate
    3234              : !> \param overlap Hermitian overlap matrix
    3235              : !> \param orbitals transformed orbitals
    3236              : ! **************************************************************************************************
    3237           10 :    SUBROUTINE strict_orbitals(c0, coordinate, overlap, orbitals)
    3238              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: c0, coordinate, overlap
    3239              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: orbitals
    3240              : 
    3241              :       COMPLEX(KIND=dp), DIMENSION(SIZE(coordinate, 2), &
    3242           20 :          SIZE(coordinate, 2))                            :: cosp, sinp, vectors
    3243              :       INTEGER                                            :: i
    3244              :       REAL(KIND=dp)                                      :: root
    3245           20 :       REAL(KIND=dp), DIMENSION(SIZE(coordinate, 2))      :: eigenvalues
    3246              : 
    3247           80 :       CALL diag_complex(MATMUL(CONJG(TRANSPOSE(coordinate)), MATMUL(overlap, coordinate)), &
    3248          580 :                         vectors, eigenvalues)
    3249           70 :       cosp(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    3250           70 :       sinp(:, :) = CMPLX(0.0_dp, 0.0_dp, KIND=dp)
    3251           30 :       DO i = 1, SIZE(eigenvalues)
    3252           20 :          root = SQRT(MAX(0.0_dp, eigenvalues(i)))
    3253           80 :          cosp = cosp + COS(root)*MATMUL(RESHAPE(vectors(:, i), [SIZE(vectors, 1), 1]), &
    3254          440 :                                         RESHAPE(CONJG(vectors(:, i)), [1, SIZE(vectors, 1)]))
    3255           30 :          IF (root < 1.0E-12_dp) THEN
    3256           16 :             sinp = sinp + MATMUL(RESHAPE(vectors(:, i), [SIZE(vectors, 1), 1]), &
    3257           88 :                                  RESHAPE(CONJG(vectors(:, i)), [1, SIZE(vectors, 1)]))
    3258              :          ELSE
    3259              :             sinp = sinp + SIN(root)/root* &
    3260           64 :                    MATMUL(RESHAPE(vectors(:, i), [SIZE(vectors, 1), 1]), &
    3261          352 :                           RESHAPE(CONJG(vectors(:, i)), [1, SIZE(vectors, 1)]))
    3262              :          END IF
    3263              :       END DO
    3264          570 :       orbitals = MATMUL(c0, cosp) + MATMUL(coordinate, sinp)
    3265              : 
    3266           10 :    END SUBROUTINE strict_orbitals
    3267              : 
    3268              : ! **************************************************************************************************
    3269              : !> \brief Energy of the independently transformed finite STRICT orbitals.
    3270              : !> \param c0 reference orbitals
    3271              : !> \param coordinate tangent coordinate
    3272              : !> \param overlap Hermitian overlap matrix
    3273              : !> \param hamiltonian Hermitian Hamiltonian
    3274              : !> \return energy
    3275              : ! **************************************************************************************************
    3276              :    FUNCTION strict_energy(c0, coordinate, overlap, hamiltonian) RESULT(energy)
    3277              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: c0, coordinate, overlap, hamiltonian
    3278              :       REAL(KIND=dp)                                      :: energy
    3279              : 
    3280              :       COMPLEX(KIND=dp), DIMENSION(SIZE(coordinate, 1), &
    3281              :          SIZE(coordinate, 2))                            :: orbitals
    3282              : 
    3283              :       CALL strict_orbitals(c0, coordinate, overlap, orbitals)
    3284              :       energy = REAL(SUM(CONJG(orbitals)*MATMUL(hamiltonian, orbitals)), KIND=dp)
    3285              : 
    3286              :    END FUNCTION strict_energy
    3287              : 
    3288              : ! **************************************************************************************************
    3289              : !> \brief Energy of finite STRICT orbitals followed by a complex occupied-space rotation.
    3290              : !> \param c0 reference orbitals
    3291              : !> \param coordinate tangent coordinate
    3292              : !> \param generator anti-Hermitian rotation generator
    3293              : !> \param overlap Hermitian overlap matrix
    3294              : !> \param hamiltonian Hermitian Hamiltonian
    3295              : !> \param occupation orbital occupations
    3296              : !> \return energy
    3297              : ! **************************************************************************************************
    3298            8 :    FUNCTION strict_rotated_energy(c0, coordinate, generator, overlap, hamiltonian, &
    3299            8 :                                   occupation) RESULT(energy)
    3300              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(IN)      :: c0, coordinate, generator, overlap, &
    3301              :                                                             hamiltonian
    3302              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: occupation
    3303              :       REAL(KIND=dp)                                      :: energy
    3304              : 
    3305              :       COMPLEX(KIND=dp), DIMENSION(SIZE(coordinate, 1), &
    3306           16 :          SIZE(coordinate, 2))                            :: orbitals
    3307              :       INTEGER                                            :: i
    3308              : 
    3309            8 :       CALL strict_orbitals(c0, coordinate, overlap, orbitals)
    3310          296 :       orbitals = MATMUL(orbitals, dense_antihermitian_exp(generator))
    3311            8 :       energy = 0.0_dp
    3312           24 :       DO i = 1, SIZE(occupation)
    3313              :          energy = energy + occupation(i)* &
    3314          296 :                   REAL(DOT_PRODUCT(orbitals(:, i), MATMUL(hamiltonian, orbitals(:, i))), KIND=dp)
    3315              :       END DO
    3316              : 
    3317            8 :    END FUNCTION strict_rotated_energy
    3318              : 
    3319              : END PROGRAM qs_ot_complex_ref_unittest
        

Generated by: LCOV version 2.0-1