LCOV - code coverage report
Current view: top level - src - floquet_utils.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:21ef868) Lines: 95.2 % 376 358
Test Date: 2026-08-14 07:04:57 Functions: 100.0 % 16 16

            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 Helper routines for the Floquet-Bloch band-structure calculation (floquet_main).
      10              : !> \par History
      11              : !> \author Shridhar Shanbhag (27.01.2026)
      12              : ! **************************************************************************************************
      13              : MODULE floquet_utils
      14              :    USE cell_types,                      ONLY: cell_type,&
      15              :                                               get_cell
      16              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_create,&
      17              :                                               cp_blacs_env_type
      18              :    USE cp_cfm_types,                    ONLY: cp_cfm_get_submatrix,&
      19              :                                               cp_cfm_set_submatrix,&
      20              :                                               cp_cfm_type
      21              :    USE cp_dbcsr_api,                    ONLY: dbcsr_p_type
      22              :    USE cp_files,                        ONLY: close_file,&
      23              :                                               open_file
      24              :    USE floquet_types,                   ONLY: floquet_env_type
      25              :    USE input_constants,                 ONLY: small_cell_full_kp
      26              :    USE kinds,                           ONLY: default_string_length,&
      27              :                                               dp,&
      28              :                                               int_8
      29              :    USE kpoint_k_r_trafo_simple,         ONLY: replicate_rs_matrices,&
      30              :                                               rs_to_kp
      31              :    USE kpoint_methods,                  ONLY: kpoint_init_cell_index
      32              :    USE kpoint_types,                    ONLY: get_kpoint_info,&
      33              :                                               kpoint_create,&
      34              :                                               kpoint_release,&
      35              :                                               kpoint_type
      36              :    USE machine,                         ONLY: m_memory_details
      37              :    USE mathconstants,                   ONLY: gaussi,&
      38              :                                               pi,&
      39              :                                               z_one,&
      40              :                                               z_zero
      41              :    USE mathlib,                         ONLY: geeig_right,&
      42              :                                               gemm_square
      43              :    USE message_passing,                 ONLY: mp_comm_split_type_shared,&
      44              :                                               mp_comm_type,&
      45              :                                               mp_para_env_type
      46              :    USE physcon,                         ONLY: a_bohr,&
      47              :                                               evolt,&
      48              :                                               kelvin
      49              :    USE post_scf_bandstructure_types,    ONLY: post_scf_bandstructure_type
      50              :    USE qs_environment_types,            ONLY: get_qs_env,&
      51              :                                               qs_environment_type
      52              :    USE qs_mo_types,                     ONLY: get_mo_set,&
      53              :                                               mo_set_type
      54              :    USE qs_moments,                      ONLY: qs_moment_kpoints_deep
      55              :    USE qs_neighbor_list_types,          ONLY: neighbor_list_set_p_type
      56              :    USE util,                            ONLY: sort
      57              : #include "./base/base_uses.f90"
      58              : 
      59              :    IMPLICIT NONE
      60              : 
      61              :    PRIVATE
      62              : 
      63              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'floquet_utils'
      64              : 
      65              :    ! Number of full Floquet-Hamiltonian copies (each n_f_size^2 complex(dp), 16 bytes per
      66              :    ! element) that the subgroup must be able to hold, distributed across its ranks.
      67              :    ! We size for 8 to leave headroom.
      68              :    INTEGER, PARAMETER, PRIVATE :: n_fm_work_copies = 8
      69              : 
      70              :    ! Public subroutines
      71              :    PUBLIC :: build_floquet_matrix, &
      72              :              compute_e_k_de_dk_dipole, &
      73              :              make_floquet_subgroups, &
      74              :              distribute_floquet_kp_data, &
      75              :              floquet_sector_weights, &
      76              :              check_floquet_convergence, &
      77              :              calculate_floquet_observables, &
      78              :              write_floquet_header, &
      79              :              write_floquet_results
      80              : 
      81              : CONTAINS
      82              : 
      83              : ! **************************************************************************************************
      84              : !> \brief ...
      85              : !> \param qs_env ...
      86              : !> \param xkp ...
      87              : !> \param e_k ...
      88              : !> \param de_dk ...
      89              : !> \param do_parallel the option to distribute the results (e_k/de_dk) across MPI ranks.
      90              : !>        When .TRUE. k-point ikp is computed and stored only on rank MOD(ikp-1,num_pe)
      91              : !>        Default .FALSE. -> every rank computes/stores all k-points (replicated).
      92              : ! **************************************************************************************************
      93            2 :    SUBROUTINE calculate_epsilon_derivative(qs_env, xkp, e_k, de_dk, do_parallel)
      94              :       TYPE(qs_environment_type), POINTER                 :: qs_env
      95              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :), &
      96              :          INTENT(IN)                                      :: xkp
      97              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
      98              :          INTENT(OUT), OPTIONAL                           :: e_k
      99              :       REAL(KIND=dp), ALLOCATABLE, &
     100              :          DIMENSION(:, :, :, :), INTENT(OUT), OPTIONAL    :: de_dk
     101              :       LOGICAL, INTENT(IN), OPTIONAL                      :: do_parallel
     102              : 
     103              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'calculate_epsilon_derivative'
     104              : 
     105            2 :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :)     :: C_dH_C, C_dS_C, C_k, dH_dk_i, dS_dk_i, &
     106            2 :                                                             H_k, S_k
     107              :       INTEGER                                            :: handle, i_dir, ikp, ispin, mepos, n, &
     108              :                                                             n_img_all, n_spin, nao, nkp, num_copy, &
     109              :                                                             num_pe
     110            2 :       INTEGER, DIMENSION(:, :), POINTER                  :: index_to_cell_all
     111            2 :       INTEGER, DIMENSION(:, :, :), POINTER               :: cell_to_index_all
     112              :       LOGICAL                                            :: my_do_parallel, present_dedk, present_ek
     113            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigenvals
     114            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :, :)  :: H_rs, S_rs
     115              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: hmat
     116              :       TYPE(cell_type), POINTER                           :: cell
     117            2 :       TYPE(dbcsr_p_type), DIMENSION(:, :), POINTER       :: matrix_ks_kp, matrix_s_kp
     118              :       TYPE(kpoint_type), POINTER                         :: kpoints_all, kpoints_scf
     119            2 :       TYPE(mo_set_type), DIMENSION(:), POINTER           :: mos
     120              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     121              :       TYPE(neighbor_list_set_p_type), DIMENSION(:), &
     122            2 :          POINTER                                         :: sab_all
     123              : 
     124            2 :       CALL timeset(routineN, handle)
     125              : 
     126            2 :       present_ek = PRESENT(e_k)  ! calculate band energies, ε_k for all kpoints xkp
     127            2 :       present_dedk = PRESENT(de_dk)  ! calculate derivative, ∇_k ε_k of band energies for all kpoints xkp
     128              : 
     129            2 :       IF (.NOT. (present_ek .OR. present_dedk)) CPABORT("Subroutine needs either e_k or de_dk")
     130              : 
     131            2 :       my_do_parallel = .FALSE.
     132            2 :       IF (PRESENT(do_parallel)) my_do_parallel = do_parallel
     133              : 
     134              :       CALL get_qs_env(qs_env, &
     135              :                       matrix_ks_kp=matrix_ks_kp, &
     136              :                       matrix_s_kp=matrix_s_kp, &
     137              :                       sab_all=sab_all, &
     138              :                       cell=cell, &
     139              :                       kpoints=kpoints_scf, &
     140              :                       para_env=para_env, &
     141            2 :                       mos=mos)
     142              : 
     143            2 :       CALL get_mo_set(mo_set=mos(1), nao=nao)
     144            2 :       CALL get_cell(cell=cell, h=hmat)
     145              : 
     146            2 :       n_spin = SIZE(matrix_ks_kp, 1)
     147            2 :       nkp = SIZE(xkp, 2)
     148              : 
     149              :       ! Distribution of k-points across ranks (mirrors qs_moment_kpoints_deep)
     150              :       ! do_parallel = .TRUE. => ikp stored in mepos==MOD(ikp-1,num_pe)
     151              :       ! do_parallel = .FALSE. => every rank computes all k-points (replicated)
     152            2 :       mepos = 0
     153            2 :       num_pe = 1
     154            2 :       num_copy = nkp
     155            2 :       IF (my_do_parallel) THEN
     156            2 :          mepos = para_env%mepos
     157            2 :          num_pe = para_env%num_pe
     158            2 :          num_copy = CEILING(REAL(nkp)/num_pe)
     159              :       END IF
     160              : 
     161              :       ! create kpoint environment kpoints_all which contains all neighbor cells R
     162              :       ! without considering any lattice symmetry
     163            2 :       NULLIFY (kpoints_all)
     164            2 :       CALL kpoint_create(kpoints_all)
     165            2 :       CALL kpoint_init_cell_index(kpoints_all, sab_all, para_env, n_img_all)
     166            2 :       CALL get_kpoint_info(kpoints_all, cell_to_index=cell_to_index_all, index_to_cell=index_to_cell_all)
     167              : 
     168           20 :       ALLOCATE (S_rs(1, nao, nao, n_img_all), H_rs(n_spin, nao, nao, n_img_all), source=0.0_dp)
     169              : 
     170              :       ! Convert real-space dbcsr matrices into arrays
     171            2 :       CALL replicate_rs_matrices(matrix_s_kp, kpoints_scf, S_rs, cell_to_index_all)
     172            2 :       CALL replicate_rs_matrices(matrix_ks_kp, kpoints_scf, H_rs, cell_to_index_all)
     173              : 
     174           12 :       IF (present_dedk) ALLOCATE (de_dk(n_spin, num_copy, 3, nao), source=0.0_dp)
     175           10 :       IF (present_ek) ALLOCATE (e_k(n_spin, num_copy, nao), source=0.0_dp)
     176              : 
     177              : !$OMP PARALLEL DEFAULT(NONE) &
     178              : !$OMP PRIVATE(ikp, ispin, S_k, H_k, eigenvals, C_k, dS_dk_i, dH_dk_i, C_dS_C, C_dH_C) &
     179              : !$OMP SHARED(nao, n_spin, de_dk, e_k, present_ek, present_dedk, mepos, num_pe, &
     180            2 : !$OMP nkp, xkp, S_rs, H_rs, index_to_cell_all, hmat)
     181              :       IF (present_dedk) ALLOCATE (dS_dk_i(nao, nao), C_dS_C(nao, nao), &
     182              :                                   dH_dk_i(nao, nao), C_dH_C(nao, nao), source=z_zero)
     183              :       ALLOCATE (C_k(nao, nao), S_k(nao, nao), H_k(nao, nao), source=z_zero)
     184              :       ALLOCATE (eigenvals(nao), source=0.0_dp)
     185              : !$OMP DO COLLAPSE(2)
     186              :       DO ispin = 1, n_spin
     187              :          DO ikp = 1, nkp
     188              :             IF (MOD(ikp - 1, num_pe) /= mepos) CYCLE
     189              : 
     190              :             ! S^R -> S(k), H^R -> H(k)
     191              :             S_k = 0
     192              :             H_k = 0
     193              :             CALL rs_to_kp(S_rs(1, :, :, :), S_k, index_to_cell_all, xkp(:, ikp))
     194              :             CALL rs_to_kp(H_rs(ispin, :, :, :), H_k, index_to_cell_all, xkp(:, ikp))
     195              : 
     196              :             ! Diagonalize H(k)C(k) = S(k)C(k)ε(k)
     197              :             CALL geeig_right(H_k, S_k, eigenvals, C_k)
     198              :             IF (present_ek) e_k(ispin, CEILING(REAL(ikp)/num_pe), :) = eigenvals(:)
     199              : 
     200              :             IF (present_dedk) THEN
     201              :                ! Evaluate the derivatives using
     202              :                ! ∇ ε_k = C^H(k) ∇ H_k C(k) - ε_k C^H(k) ∇ S_k C(k)
     203              :                DO i_dir = 1, 3
     204              :                   CALL rs_to_kp(S_rs(1, :, :, :), dS_dk_i, index_to_cell_all, xkp(:, ikp), i_dir, hmat)
     205              :                   CALL rs_to_kp(H_rs(ispin, :, :, :), dH_dk_i, index_to_cell_all, xkp(:, ikp), i_dir, hmat)
     206              : 
     207              :                   CALL gemm_square(C_k, 'C', dS_dk_i, 'N', C_k, 'N', C_dS_C)
     208              :                   CALL gemm_square(C_k, 'C', dH_dk_i, 'N', C_k, 'N', C_dH_C)
     209              : 
     210              :                   DO n = 1, nao
     211              :                      de_dk(ispin, CEILING(REAL(ikp)/num_pe), i_dir, n) = &
     212              :                         DBLE(C_dH_C(n, n)) - DBLE(eigenvals(n)*C_dS_C(n, n))
     213              :                   END DO
     214              :                END DO
     215              :             END IF
     216              :          END DO
     217              :       END DO
     218              : !$OMP END DO
     219              :       IF (present_dedk) DEALLOCATE (dS_dk_i, C_dS_C, dH_dk_i, C_dH_C)
     220              :       DEALLOCATE (S_k, H_k, C_k, eigenvals)
     221              : !$OMP END PARALLEL
     222            2 :       DEALLOCATE (S_rs, H_rs)
     223            2 :       CALL kpoint_release(kpoints_all)
     224              : 
     225            2 :       CALL timestop(handle)
     226              : 
     227            8 :    END SUBROUTINE calculate_epsilon_derivative
     228              : 
     229              : ! **************************************************************************************************
     230              : !> \brief Momentum matrix elements p_nm(k) for one k-point and one spin.
     231              : !> \param e_k_kp_spin ...
     232              : !> \param de_dk_kp_spin ...
     233              : !> \param dipole_kp_spin ...
     234              : !> \param momentum ...
     235              : ! **************************************************************************************************
     236            2 :    SUBROUTINE build_momentum_matrix(e_k_kp_spin, de_dk_kp_spin, dipole_kp_spin, momentum)
     237              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: e_k_kp_spin
     238              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: de_dk_kp_spin
     239              :       COMPLEX(KIND=dp), DIMENSION(:, :, :), INTENT(IN)   :: dipole_kp_spin
     240              :       COMPLEX(KIND=dp), DIMENSION(:, :, :), INTENT(OUT)  :: momentum
     241              : 
     242              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_momentum_matrix'
     243              : 
     244              :       INTEGER                                            :: handle, i, i_dir, j, nao
     245              : 
     246            2 :       CALL timeset(routineN, handle)
     247              : 
     248            2 :       nao = SIZE(e_k_kp_spin)
     249              : 
     250              :       ! We calculate momentum matrix elements p_nm = <ψ_n|-iħ∇_r|ψ_m>
     251              :       ! p_nm = <u_n|(ħk - iħ∇_r)|u_m>
     252              :       ! p_nm = i d_nm (ε_n - ε_m) + ∇_k ε_n δ_nm
     253              : 
     254              : !$OMP PARALLEL DEFAULT(NONE) &
     255              : !$OMP PRIVATE(i_dir, i, j) &
     256            2 : !$OMP SHARED(nao, momentum, dipole_kp_spin, e_k_kp_spin, de_dk_kp_spin)
     257              : !$OMP DO COLLAPSE(3)
     258              :       DO i_dir = 1, 3
     259              :          DO i = 1, nao
     260              :             DO j = 1, nao
     261              :                IF (j == i) THEN
     262              :                   momentum(i_dir, i, j) = de_dk_kp_spin(i_dir, i)
     263              :                ELSE
     264              :                   momentum(i_dir, i, j) = gaussi*(e_k_kp_spin(i) - e_k_kp_spin(j))* &
     265              :                                           dipole_kp_spin(i_dir, i, j)
     266              :                END IF
     267              :             END DO
     268              :          END DO
     269              :       END DO
     270              : !$OMP END DO
     271              : !$OMP END PARALLEL
     272              : 
     273            2 :       CALL timestop(handle)
     274            2 :    END SUBROUTINE build_momentum_matrix
     275              : 
     276              : ! **************************************************************************************************
     277              : !> \brief Off-diagonal coupling block of H_F(k) for one k-point and spin, from precomputed data.
     278              : !> \param bs_env ...
     279              : !> \param e_k_kp_spin ...
     280              : !> \param de_dk_kp_spin ...
     281              : !> \param dipole_kp_spin ...
     282              : !> \param off_diag_m ...
     283              : ! **************************************************************************************************
     284            2 :    SUBROUTINE build_off_diagonal_matrix(bs_env, e_k_kp_spin, de_dk_kp_spin, dipole_kp_spin, off_diag_m)
     285              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     286              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: e_k_kp_spin
     287              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: de_dk_kp_spin
     288              :       COMPLEX(KIND=dp), DIMENSION(:, :, :), INTENT(IN)   :: dipole_kp_spin
     289              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: off_diag_m
     290              : 
     291              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_off_diagonal_matrix'
     292              : 
     293              :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :)  :: momentum
     294              :       COMPLEX(KIND=dp), DIMENSION(3)                     :: efactor
     295              :       INTEGER                                            :: handle, i, i_dir, j, nao
     296              :       REAL(KIND=dp)                                      :: amplitude, omega
     297              :       REAL(KIND=dp), DIMENSION(3)                        :: e_vec, phi, polarisation
     298              : 
     299            2 :       CALL timeset(routineN, handle)
     300              : 
     301              :       ! Builds the matrix that occupies the off diagonal blocks in the Floquet Matrix H_F
     302              : 
     303            2 :       nao = SIZE(e_k_kp_spin)
     304              : 
     305            8 :       polarisation(:) = bs_env%floquet_polarisation(:)
     306            8 :       IF (SQRT(SUM(polarisation**2)) < EPSILON(0.0_dp)) THEN
     307            0 :          CPABORT("Invalid (too small) polarisation vector specified for POLARISATION")
     308              :       END IF
     309              : 
     310            2 :       amplitude = bs_env%floquet_amplitude
     311            8 :       e_vec(:) = amplitude*polarisation
     312            8 :       phi(:) = pi*bs_env%floquet_phi(:)
     313            2 :       omega = bs_env%floquet_omega
     314              : 
     315            8 :       ALLOCATE (momentum(3, nao, nao), source=z_zero)
     316            2 :       CALL build_momentum_matrix(e_k_kp_spin, de_dk_kp_spin, dipole_kp_spin, momentum)
     317              : 
     318              :       ! E_factor(α) = (i E(α) exp(i·φ(α)))/(2ω), where α = x,y,z
     319            8 :       DO i_dir = 1, 3
     320            8 :          efactor(i_dir) = gaussi*e_vec(i_dir)*CMPLX(COS(phi(i_dir)), SIN(phi(i_dir)), KIND=dp)/(2*omega)
     321              :       END DO
     322              : 
     323              :       ! off_diag_uv = Σ_α [p_uv^α(k) · E_factor(α)], where α = x,y,z
     324          146 :       off_diag_m(:, :) = z_zero
     325            8 :       DO i_dir = 1, 3
     326           56 :          DO i = 1, nao
     327          438 :             DO j = 1, nao
     328              :                off_diag_m(i, j) = off_diag_m(i, j) + &
     329          432 :                                   momentum(i_dir, i, j)*efactor(i_dir)
     330              :             END DO
     331              :          END DO
     332              :       END DO
     333            2 :       DEALLOCATE (momentum)
     334              : 
     335            2 :       CALL timestop(handle)
     336              : 
     337            2 :    END SUBROUTINE build_off_diagonal_matrix
     338              : 
     339              : ! **************************************************************************************************
     340              : !> \brief Diagonal block of H_F(k) for Floquet sector f_index.
     341              : !> \param bs_env ...
     342              : !> \param e_k_kp_spin ...
     343              : !> \param f_index Floquet sector index
     344              : !> \param diag_e ...
     345              : ! **************************************************************************************************
     346          202 :    SUBROUTINE build_diagonal_matrix(bs_env, e_k_kp_spin, f_index, diag_e)
     347              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     348              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: e_k_kp_spin
     349              :       INTEGER, INTENT(IN)                                :: f_index
     350              :       COMPLEX(KIND=dp), DIMENSION(:, :), INTENT(OUT)     :: diag_e
     351              : 
     352              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_diagonal_matrix'
     353              : 
     354              :       INTEGER                                            :: handle, i, nao
     355              :       REAL(KIND=dp)                                      :: omega
     356              : 
     357          202 :       CALL timeset(routineN, handle)
     358              :       ! Builds the diagonal block of the H_F for Floquet sector index f_index
     359              :       ! Equilibrium band energies shifted by f_index·ħΩ, i.e., diag_e(n,n) = ε_{nk} + f_index·ħΩ.
     360          202 :       nao = SIZE(e_k_kp_spin)
     361          202 :       omega = bs_env%floquet_omega
     362        14746 :       diag_e(:, :) = z_zero
     363         1818 :       DO i = 1, nao
     364         1818 :          diag_e(i, i) = e_k_kp_spin(i) + f_index*omega
     365              :       END DO
     366              : 
     367          202 :       CALL timestop(handle)
     368              : 
     369          202 :    END SUBROUTINE build_diagonal_matrix
     370              : 
     371              : ! **************************************************************************************************
     372              : !> \brief Builds the Floquet-Bloch Hamiltonian H_F(k) for a single k-point and spin channel from
     373              : !>        the band data currently held in floquet_env.
     374              : !> \param bs_env ...
     375              : !> \param floquet_env ...
     376              : !> \param floquet_matrix ...
     377              : ! **************************************************************************************************
     378            2 :    SUBROUTINE build_floquet_matrix(bs_env, floquet_env, floquet_matrix)
     379              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     380              :       TYPE(floquet_env_type), INTENT(IN)                 :: floquet_env
     381              :       TYPE(cp_cfm_type), INTENT(IN)                      :: floquet_matrix
     382              : 
     383              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'build_floquet_matrix'
     384              : 
     385              :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :)     :: conj_off_diag_m, diag_e, off_diag_m
     386              :       INTEGER                                            :: f_index, handle, i, i_f, max_f_index, &
     387              :                                                             n_fbands, nao
     388              : 
     389            2 :       CALL timeset(routineN, handle)
     390              : 
     391            2 :       nao = floquet_env%nao
     392            2 :       max_f_index = floquet_env%max_f_index
     393            2 :       n_fbands = 1 + 2*max_f_index
     394              : 
     395              :       ! Creates the floquet matrix H_F by placing the diagonal and off diagonal blocks
     396            8 :       ALLOCATE (diag_e(nao, nao), source=z_zero)
     397            6 :       ALLOCATE (off_diag_m(nao, nao), source=z_zero)
     398            6 :       ALLOCATE (conj_off_diag_m(nao, nao), source=z_zero)
     399              : 
     400              :       CALL build_off_diagonal_matrix(bs_env, floquet_env%e_k_kp_spin, floquet_env%de_dk_kp_spin, &
     401            2 :                                      floquet_env%dipole_kp_spin, off_diag_m)
     402          146 :       conj_off_diag_m(:, :) = CONJG(TRANSPOSE(off_diag_m(:, :)))
     403              : 
     404       654482 :       floquet_matrix%local_data(:, :) = z_zero
     405          204 :       DO i = 1, n_fbands
     406          202 :          i_f = 1 + (i - 1)*nao
     407          202 :          f_index = i - max_f_index - 1
     408          202 :          CALL build_diagonal_matrix(bs_env, floquet_env%e_k_kp_spin, f_index, diag_e)
     409          202 :          CALL cp_cfm_set_submatrix(floquet_matrix, diag_e, i_f, i_f)
     410          204 :          IF (i > 1) THEN
     411          200 :             CALL cp_cfm_set_submatrix(floquet_matrix, off_diag_m, i_f, i_f - nao)
     412          200 :             CALL cp_cfm_set_submatrix(floquet_matrix, conj_off_diag_m, i_f - nao, i_f)
     413              :          END IF
     414              :       END DO
     415            2 :       DEALLOCATE (diag_e, off_diag_m, conj_off_diag_m)
     416              : 
     417            2 :       CALL timestop(handle)
     418            2 :    END SUBROUTINE build_floquet_matrix
     419              : 
     420              : ! **************************************************************************************************
     421              : !> \brief Make this k-point/spin's band data available to every rank of the owning subgroup.
     422              : !> \param para_env the global parallel environment
     423              : !> \param para_env_sub the subgroup parallel environment
     424              : !> \param ispin the spin channel
     425              : !> \param ikp the DOS k-point index
     426              : !> \param e_k band energies for all DOS k-points, distributed
     427              : !> \param de_dk band-energy k-derivatives, distributed
     428              : !> \param dipole dipole matrix elements, distributed
     429              : !> \param floquet_env ...
     430              : ! **************************************************************************************************
     431            2 :    SUBROUTINE distribute_floquet_kp_data(para_env, para_env_sub, ispin, ikp, e_k, de_dk, dipole, &
     432              :                                          floquet_env)
     433              :       TYPE(mp_para_env_type), POINTER                    :: para_env, para_env_sub
     434              :       INTEGER, INTENT(IN)                                :: ispin, ikp
     435              :       REAL(KIND=dp), DIMENSION(:, :, :), INTENT(IN)      :: e_k
     436              :       REAL(KIND=dp), DIMENSION(:, :, :, :), INTENT(IN)   :: de_dk
     437              :       COMPLEX(KIND=dp), DIMENSION(:, :, :, :, :), &
     438              :          INTENT(IN)                                      :: dipole
     439              :       TYPE(floquet_env_type), INTENT(INOUT)              :: floquet_env
     440              : 
     441              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'distribute_floquet_kp_data'
     442              : 
     443              :       INTEGER                                            :: handle, loc_idx, local_src, owner
     444              : 
     445            2 :       CALL timeset(routineN, handle)
     446              : 
     447              :       ! Identify the owner's rank within the subgroup (all subgroup ranks agree on local_src).
     448            2 :       owner = MOD(ikp - 1, para_env%num_pe)
     449              : 
     450            2 :       local_src = -1
     451            2 :       IF (para_env%mepos == owner) local_src = para_env_sub%mepos
     452            2 :       CALL para_env_sub%max(local_src)
     453              : 
     454              :       ! The owner copies its single-spin slice of this k-point's distributed band data into the
     455              :       ! broadcast buffers, then broadcasts them to the whole subgroup.
     456            2 :       IF (para_env%mepos == owner) THEN
     457            1 :          loc_idx = CEILING(REAL(ikp)/para_env%num_pe)
     458            9 :          floquet_env%e_k_kp_spin(:) = e_k(ispin, loc_idx, :)
     459           33 :          floquet_env%de_dk_kp_spin(:, :) = de_dk(ispin, loc_idx, :, :)
     460          265 :          floquet_env%dipole_kp_spin(:, :, :) = dipole(ispin, loc_idx, :, :, :)
     461              :       END IF
     462            2 :       CALL para_env_sub%bcast(floquet_env%e_k_kp_spin, local_src)
     463            2 :       CALL para_env_sub%bcast(floquet_env%de_dk_kp_spin, local_src)
     464            2 :       CALL para_env_sub%bcast(floquet_env%dipole_kp_spin, local_src)
     465              : 
     466            2 :       CALL timestop(handle)
     467              : 
     468            2 :    END SUBROUTINE distribute_floquet_kp_data
     469              : 
     470              : ! **************************************************************************************************
     471              : !> \brief Precompute, on the global communicator, the band quantities needed to assemble the
     472              : !>        Floquet-Bloch Hamiltonian for all DOS k-points (all spins), distributed across ranks.
     473              : !> \param qs_env ...
     474              : !> \param bs_env ...
     475              : !> \param e_k ...
     476              : !> \param de_dk ...
     477              : !> \param dipole ...
     478              : ! **************************************************************************************************
     479            2 :    SUBROUTINE compute_e_k_de_dk_dipole(qs_env, bs_env, e_k, de_dk, dipole)
     480              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     481              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     482              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
     483              :          INTENT(OUT)                                     :: e_k
     484              :       REAL(KIND=dp), ALLOCATABLE, &
     485              :          DIMENSION(:, :, :, :), INTENT(OUT)              :: de_dk
     486              :       COMPLEX(KIND=dp), ALLOCATABLE, &
     487              :          DIMENSION(:, :, :, :, :), INTENT(OUT)           :: dipole
     488              : 
     489              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_e_k_de_dk_dipole'
     490              : 
     491              :       INTEGER                                            :: handle, ikp, nkp_only_bs, nkp_start
     492              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: xkp_all
     493              : 
     494            2 :       CALL timeset(routineN, handle)
     495              : 
     496              :       ! The band-structure k-points are the last nkp_only_bs entries of kpoints_DOS%xkp, stored
     497              :       ! after the nkp_only_DOS DOS-only k-points.
     498            2 :       nkp_start = bs_env%nkp_only_DOS
     499            2 :       nkp_only_bs = bs_env%nkp_only_bs
     500              : 
     501              :       ! Collect the coordinates of all band-structure k-points (global index nkp_start + local).
     502            6 :       ALLOCATE (xkp_all(3, nkp_only_bs))
     503            4 :       DO ikp = 1, nkp_only_bs
     504           10 :          xkp_all(:, ikp) = bs_env%kpoints_DOS%xkp(1:3, nkp_start + ikp)
     505              :       END DO
     506              : 
     507              :       ! Calculate and distribute the results across ranks
     508              :       ! One k-point per rank, round-robin by global rank
     509              :       ! So the results for ikp are stored in mepos==MOD(ikp-1,num_pe)
     510            2 :       CALL calculate_epsilon_derivative(qs_env, xkp_all, e_k=e_k, de_dk=de_dk, do_parallel=.TRUE.)
     511            2 :       CALL qs_moment_kpoints_deep(qs_env, xkp_all, dipole, do_parallel=.TRUE.)
     512              : 
     513            2 :       DEALLOCATE (xkp_all)
     514              : 
     515            2 :       CALL timestop(handle)
     516            2 :    END SUBROUTINE compute_e_k_de_dk_dipole
     517              : 
     518              : ! **************************************************************************************************
     519              : !> \brief Finds the number of MPI ranks that share a physical node, determined by splitting the
     520              : !>        given communicator into node-local communicators via MPI's shared-memory split type.
     521              : !> \param para_env ...
     522              : !> \return the number of ranks on the calling rank's node
     523              : ! **************************************************************************************************
     524            2 :    FUNCTION find_ranks_per_node(para_env) RESULT(ranks_per_node)
     525              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     526              :       INTEGER                                            :: ranks_per_node
     527              : 
     528              :       TYPE(mp_comm_type)                                 :: node_comm
     529              : 
     530            2 :       CALL node_comm%from_split_type(para_env, mp_comm_split_type_shared, key=para_env%mepos)
     531            2 :       ranks_per_node = MAX(1, node_comm%num_pe)
     532            2 :       CALL node_comm%free()
     533              : 
     534            2 :    END FUNCTION find_ranks_per_node
     535              : 
     536              : ! **************************************************************************************************
     537              : !> \brief Choose the optimal number of MPI ranks per subgroup for Floquet calculations.
     538              : !> \param qs_env ...
     539              : !> \param bs_env ...
     540              : !> \return the chosen number of ranks per subgroup (1 .. num_pe)
     541              : ! **************************************************************************************************
     542            4 :    FUNCTION floquet_determine_subgroup_size(qs_env, bs_env) RESULT(group_size)
     543              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     544              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     545              :       INTEGER                                            :: group_size
     546              : 
     547              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'floquet_determine_subgroup_size'
     548              : 
     549              :       INTEGER                                            :: g_load, g_mem, handle, n_f_size, nao, &
     550              :                                                             nkp_only_bs, ranks_per_node, unit_nr
     551              :       INTEGER(KIND=int_8)                                :: Buffers, Cached, MemFree, MemLikelyFree, &
     552              :                                                             MemTotal, needed_bytes, Slab, &
     553              :                                                             SReclaimable, usable_per_rank
     554              :       REAL(KIND=dp)                                      :: mem_fill_fraction
     555            2 :       TYPE(mo_set_type), DIMENSION(:), POINTER           :: mos
     556              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     557              : 
     558            2 :       CALL timeset(routineN, handle)
     559              : 
     560            2 :       CALL get_qs_env(qs_env, para_env=para_env, mos=mos)
     561            2 :       CALL get_mo_set(mo_set=mos(1), nao=nao)
     562              : 
     563            2 :       unit_nr = bs_env%unit_nr
     564            2 :       n_f_size = nao*(1 + 2*bs_env%max_floquet_index)
     565            2 :       nkp_only_bs = bs_env%nkp_only_bs
     566            2 :       mem_fill_fraction = bs_env%floquet_mem_fill_fraction
     567              : 
     568              :       ! We choose the subgroup size to be the larger of two floors:
     569              :       ! 1. Memory floor G_mem: the subgroup must jointly hold the Floquet working set,
     570              :       !    G_mem = ceil(needed_bytes/usable per-rank memory), where the usable memory is
     571              :       !    the mem_fill_fraction of the per-rank FREE memory.
     572              :       ! 2. K-point floor G_load = floor(num_pe / nkp_only_bs): the LARGEST G that still
     573              :       !    yields at least nkp_only_bs subgroups (floor(num_pe/G) >= nkp_only_bs). When ranks
     574              :       !    outnumber k-points this hands every k-point its own (larger, hence faster)
     575              :       !    subgroup instead of leaving spare ranks idle.
     576              : 
     577              :       ! usable_per_rank = a fraction of the per-rank FREE memory, not including SCF and other data
     578            2 :       CALL m_memory_details(MemTotal, MemFree, Buffers, Cached, Slab, SReclaimable, MemLikelyFree)
     579            2 :       ranks_per_node = find_ranks_per_node(para_env)
     580            2 :       usable_per_rank = INT(mem_fill_fraction*REAL(MemFree, dp), int_8)/INT(ranks_per_node, int_8)
     581              : 
     582              :       ! Total memory the subgroup must hold: n_fm_work_copies copies of the Floquet
     583              :       ! Hamiltonian, each n_f_size^2 complex(dp) numbers at 16 bytes each.
     584            2 :       needed_bytes = 16_int_8*INT(n_fm_work_copies, int_8)*INT(n_f_size, int_8)**2
     585              : 
     586            2 :       IF (usable_per_rank <= 0_int_8) THEN
     587              :          ! Memory info unavailable (e.g. no /proc found) use one subgroup spanning all ranks
     588            0 :          g_mem = para_env%num_pe
     589              :       ELSE
     590              :          ! Memory floor: smallest G with needed_bytes/G <= usable_per_rank
     591              :          g_mem = INT(MIN((needed_bytes + usable_per_rank - 1_int_8)/usable_per_rank, &
     592            2 :                          INT(para_env%num_pe, int_8)))
     593              :       END IF
     594              : 
     595            2 :       IF (g_mem > para_env%num_pe) CPWARN("Total Memory Likely Insufficient, process may be killed")
     596              : 
     597              :       ! K-point floor: floor(num_pe / nkp_only_bs) = largest G that gives at least nkp_only_bs subgroups
     598            2 :       g_load = para_env%num_pe/MAX(nkp_only_bs, 1)
     599              : 
     600            2 :       group_size = MAX(g_mem, g_load)
     601            2 :       group_size = MIN(group_size, para_env%num_pe)
     602            2 :       group_size = MAX(group_size, 1)
     603            2 :       CALL para_env%max(group_size)
     604              : 
     605            2 :       IF (unit_nr > 0) THEN
     606            1 :          WRITE (unit_nr, '(T2,A)') ""
     607            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | Detected MPI ranks per node:", ranks_per_node
     608            1 :          WRITE (unit_nr, '(T2,A,T66,I12,A)') "FLOQUET MEMORY | Free memory per rank:", &
     609            2 :             (MemLikelyFree/INT(ranks_per_node, int_8))/1048576_int_8, " MB"
     610            1 :          WRITE (unit_nr, '(T2,A,T66,I12,A)') "FLOQUET MEMORY | Usable per rank (reserve applied):", &
     611            2 :             usable_per_rank/1048576_int_8, " MB"
     612            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | Floquet copies needed:", n_fm_work_copies
     613            1 :          WRITE (unit_nr, '(T2,A,T66,I12,A)') "FLOQUET MEMORY | Total Floquet working set:", &
     614            2 :             needed_bytes/1048576_int_8, " MB"
     615            1 :          WRITE (unit_nr, '(T2,A,T66,I12,A)') "FLOQUET MEMORY | Floquet working set per rank:", &
     616            2 :             (needed_bytes/INT(MAX(group_size, 1), int_8))/1048576_int_8, " MB"
     617            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | Memory floor (ranks/subgroup):", g_mem
     618            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | K-point floor (ranks/subgroup):", g_load
     619            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | Chosen MPI ranks per subgroup:", group_size
     620              :       END IF
     621              : 
     622            2 :       CALL timestop(handle)
     623            2 :    END FUNCTION floquet_determine_subgroup_size
     624              : 
     625              : ! **************************************************************************************************
     626              : !> \brief Split the para_env into subgroups so that the Floquet Hamiltonian is distributed across
     627              : !>        the ranks of each subgroup. Also creates the subgroup BLACS context.
     628              : !> \param qs_env ...
     629              : !> \param bs_env ...
     630              : !> \param para_env_sub the created subgroup parallel environment
     631              : !> \param blacs_env_sub the created subgroup BLACS context
     632              : !> \param group_distribution subgroup index of every global rank, 0:num_pe-1
     633              : !> \param ngroups the number of subgroups created
     634              : ! **************************************************************************************************
     635            2 :    SUBROUTINE make_floquet_subgroups(qs_env, bs_env, para_env_sub, blacs_env_sub, &
     636              :                                      group_distribution, ngroups)
     637              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     638              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     639              :       TYPE(mp_para_env_type), POINTER                    :: para_env_sub
     640              :       TYPE(cp_blacs_env_type), POINTER                   :: blacs_env_sub
     641              :       INTEGER, ALLOCATABLE, DIMENSION(:), INTENT(OUT)    :: group_distribution
     642              :       INTEGER, INTENT(OUT)                               :: ngroups
     643              : 
     644              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'make_floquet_subgroups'
     645              : 
     646              :       INTEGER                                            :: group_size, handle, n_spin, nkp_only_bs, &
     647              :                                                             stride_kp, unit_nr
     648              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     649              : 
     650            2 :       CALL timeset(routineN, handle)
     651              : 
     652            2 :       CALL get_qs_env(qs_env, para_env=para_env)
     653            2 :       unit_nr = bs_env%unit_nr
     654            2 :       nkp_only_bs = bs_env%nkp_only_bs
     655            2 :       n_spin = bs_env%n_spin
     656              : 
     657              :       ! We split the global communicator into subgroups with the Floquet Hamiltonian distributed
     658              :       ! across the processes in each subgroup and the k-points distributed across subgroups.
     659            2 :       group_size = floquet_determine_subgroup_size(qs_env, bs_env)
     660              : 
     661            2 :       IF (nkp_only_bs < para_env%num_pe) THEN
     662            2 :          stride_kp = para_env%num_pe/group_size
     663              :       ELSE
     664            0 :          stride_kp = 1
     665              :       END IF
     666            6 :       ALLOCATE (group_distribution(0:para_env%num_pe - 1))
     667            2 :       ALLOCATE (para_env_sub)
     668              :       CALL para_env_sub%from_split(comm=para_env, ngroups=ngroups, &
     669              :                                    group_distribution=group_distribution, &
     670            2 :                                    subgroup_min_size=group_size, stride=stride_kp)
     671            2 :       IF (unit_nr > 0) THEN
     672            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | Subgroup rank stride:", stride_kp
     673            1 :          WRITE (unit_nr, '(T2,A,T66,I15)') "FLOQUET MEMORY | Number of subgroups:", ngroups
     674            1 :          WRITE (unit_nr, '(/,T2,A,I5,A,I1,A)') "FLOQUET CALCULATIONS PROGRESS OUT OF", nkp_only_bs, &
     675            2 :             " K-POINTS AND ", n_spin, " SPINS"
     676              :       END IF
     677              : 
     678            2 :       NULLIFY (blacs_env_sub)
     679            2 :       CALL cp_blacs_env_create(blacs_env_sub, para_env_sub)
     680              : 
     681            2 :       CALL timestop(handle)
     682              : 
     683            2 :    END SUBROUTINE make_floquet_subgroups
     684              : 
     685              : ! **************************************************************************************************
     686              : !> \brief Central and boundary-sector weights of every Floquet eigenvector, stored into
     687              : !>        floquet_env%w0 and floquet_env%wE.
     688              : !> \param floquet_env ...
     689              : !> \param cfm_eigenvectors Floquet eigenvectors
     690              : ! **************************************************************************************************
     691            2 :    SUBROUTINE floquet_sector_weights(floquet_env, cfm_eigenvectors)
     692              :       TYPE(floquet_env_type), INTENT(INOUT)              :: floquet_env
     693              :       TYPE(cp_cfm_type), INTENT(IN)                      :: cfm_eigenvectors
     694              : 
     695              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'floquet_sector_weights'
     696              : 
     697              :       COMPLEX(KIND=dp), ALLOCATABLE, DIMENSION(:, :)     :: v_block
     698              :       INTEGER                                            :: handle, max_f_index, n_f_size, nao
     699              : 
     700            2 :       CALL timeset(routineN, handle)
     701              : 
     702            2 :       nao = floquet_env%nao
     703            2 :       max_f_index = floquet_env%max_f_index
     704            2 :       n_f_size = floquet_env%n_f_size
     705              : 
     706            8 :       ALLOCATE (v_block(nao, n_f_size))
     707              : 
     708              :       ! Evaluate w0_α = Σ_{n=1..nao} |<n,m=0|α>|^2 (central sector)
     709            2 :       CALL cp_cfm_get_submatrix(cfm_eigenvectors, v_block, 1 + nao*max_f_index, 1)
     710        14546 :       floquet_env%w0(:) = SUM(ABS(v_block)**2, DIM=1)
     711              : 
     712              :       ! Evaluate wE_α = Σ_{n=1..nao} (|<n,m=-M|α>|^2 + |<n,m=+M|α>|^2) (outermost rungs)
     713         1618 :       floquet_env%wE(:) = 0.0_dp
     714            2 :       IF (max_f_index >= 1) THEN
     715            2 :          CALL cp_cfm_get_submatrix(cfm_eigenvectors, v_block, 1, 1)
     716        14546 :          floquet_env%wE(:) = SUM(ABS(v_block)**2, DIM=1)
     717              : 
     718            2 :          CALL cp_cfm_get_submatrix(cfm_eigenvectors, v_block, 1 + nao*2*max_f_index, 1)
     719        14546 :          floquet_env%wE(:) = floquet_env%wE(:) + SUM(ABS(v_block)**2, DIM=1)
     720              :       END IF
     721              : 
     722            2 :       DEALLOCATE (v_block)
     723              : 
     724              :       ! The orthonormal rows of a unitary matrix are such that Σ_α w0_α = nao.
     725         1618 :       CPASSERT(ABS(SUM(floquet_env%w0) - REAL(nao, dp)) < 1.0E-6_dp*REAL(nao, dp))
     726              : 
     727            2 :       CALL timestop(handle)
     728              : 
     729            2 :    END SUBROUTINE floquet_sector_weights
     730              : 
     731              : ! **************************************************************************************************
     732              : !> \brief Checks that MAX_FLOQUET_INDEX is large enough and the Floquet Hamiltonian was
     733              : !>        truncated far enough away from the central sector to leave it unperturbed.
     734              : !> \param bs_env ...
     735              : !> \param floquet_env holds the central- (w0) and outermost-sector (wE) weights
     736              : ! **************************************************************************************************
     737            2 :    SUBROUTINE check_floquet_convergence(bs_env, floquet_env)
     738              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     739              :       TYPE(floquet_env_type), INTENT(IN)                 :: floquet_env
     740              : 
     741              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'check_floquet_convergence'
     742              : 
     743              :       CHARACTER(LEN=default_string_length)               :: msg
     744              :       INTEGER                                            :: handle
     745              :       REAL(KIND=dp)                                      :: leak
     746              : 
     747              :       ! The truncation is converged if m=0 states have small weight on outermost rungs m = ±M
     748            2 :       CALL timeset(routineN, handle)
     749              : 
     750              :       ! MAX_FLOQUET_INDEX = 0 means no sidebands at all
     751            2 :       IF (bs_env%max_floquet_index < 1) THEN
     752            0 :          CALL timestop(handle)
     753            0 :          RETURN
     754              :       END IF
     755              : 
     756              :       ! No cleanly m=0-dominated state exists: the drive has hybridised every band with
     757              :       ! its sidebands, strong field but not a truncation failure.
     758          630 :       IF (.NOT. ANY(floquet_env%w0 > 0.5_dp)) THEN
     759            0 :          CPWARN("Floquet: no m=0-dominated state; cannot assess MAX_FLOQUET_INDEX convergence.")
     760            0 :          CALL timestop(handle)
     761            0 :          RETURN
     762              :       END IF
     763              : 
     764         1618 :       leak = MAXVAL(floquet_env%wE, MASK=(floquet_env%w0 > 0.5_dp))
     765              : 
     766            2 :       IF (bs_env%eps_floquet > 0.0_dp .AND. leak > bs_env%eps_floquet) THEN
     767              :          WRITE (msg, '(A,ES10.2E2,A,ES10.2E2)') &
     768            0 :             "MAX_FLOQUET_INDEX is too small. Leak: ", leak, "exceeds EPS_FLOQUET: ", bs_env%eps_floquet
     769            0 :          CPABORT(TRIM(msg))
     770              :       END IF
     771              : 
     772            2 :       CALL timestop(handle)
     773              : 
     774              :    END SUBROUTINE check_floquet_convergence
     775              : 
     776              : ! **************************************************************************************************
     777              : !> \brief Energy origin for the Floquet calculations, chosen to be the valence band maximum.
     778              : !> \param bs_env ...
     779              : !> \return the valence band maximum
     780              : ! **************************************************************************************************
     781            3 :    FUNCTION floquet_reference_energy(bs_env) RESULT(mu)
     782              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     783              :       REAL(KIND=dp)                                      :: mu
     784              : 
     785            3 :       IF (bs_env%do_gw .OR. &
     786              :           bs_env%small_cell_full_kp_or_large_cell_Gamma == small_cell_full_kp) THEN
     787            3 :          mu = bs_env%band_edges_scf%VBM
     788            0 :       ELSE IF (bs_env%n_spin == 1) THEN
     789            0 :          mu = bs_env%eigenval_scf_Gamma(bs_env%n_occ(1), 1)
     790              :       ELSE
     791              :          mu = MAX(bs_env%eigenval_scf_Gamma(bs_env%n_occ(1), 1), &
     792            0 :                   bs_env%eigenval_scf_Gamma(bs_env%n_occ(2), 2))
     793              :       END IF
     794              : 
     795            3 :    END FUNCTION floquet_reference_energy
     796              : 
     797              : ! **************************************************************************************************
     798              : !> \brief Calculate all Floquet observables for one k-point and spin: the DOS (a_k), the m=0 bands,
     799              : !>        the quasi-energies, and store in floquet_env.
     800              : !> \param bs_env ...
     801              : !> \param para_env_sub ...
     802              : !> \param ispin ...
     803              : !> \param ikp ...
     804              : !> \param floquet_env holds eigenvalues, w0 (input) and a_k, m0_energies, m0_weights,
     805              : !>        quasi_energies (output)
     806              : ! **************************************************************************************************
     807            2 :    SUBROUTINE calculate_floquet_observables(bs_env, para_env_sub, ispin, ikp, floquet_env)
     808              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     809              :       TYPE(mp_para_env_type), POINTER                    :: para_env_sub
     810              :       INTEGER, INTENT(IN)                                :: ispin, ikp
     811              :       TYPE(floquet_env_type), INTENT(INOUT)              :: floquet_env
     812              : 
     813              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'calculate_floquet_observables'
     814              : 
     815              :       INTEGER                                            :: handle, i, i_E, j, n_E, n_f_size, nao
     816            2 :       INTEGER, ALLOCATABLE, DIMENSION(:)                 :: work
     817              :       REAL(KIND=dp)                                      :: broad, cum, E_min, energy, energy_step, &
     818              :                                                             mu, omega, target_level
     819              : 
     820            2 :       CALL timeset(routineN, handle)
     821              : 
     822            2 :       n_f_size = floquet_env%n_f_size
     823            2 :       nao = floquet_env%nao
     824            2 :       n_E = floquet_env%n_E
     825              : 
     826            2 :       mu = floquet_reference_energy(bs_env)
     827            2 :       omega = bs_env%floquet_omega
     828            2 :       broad = bs_env%broadening_floquet
     829            2 :       energy_step = bs_env%energy_step_floquet
     830            2 :       E_min = mu - bs_env%energy_window_floquet
     831              : 
     832              :       ! (1) Spectral function / DOS, A(k,E) = -(1/π) Im Tr_KS G^R_00(k,E),
     833              :       ! where G^R_00(k,E) = (E + iη - H_F)^(-1) is the retarded Green's function
     834              :       ! With H_F = Σ_α λ_α |α><α|, we simplify
     835              :       ! A(k,E) = -(1/π) Σ_α w0_α Im[1/(E + iη - λ_α)], η = broadening/2.
     836              :       ! This avoids the per-energy dense inversion of the full H_F.
     837              : 
     838            6 :       DO i_E = 1, n_E
     839            4 :          energy = E_min + i_E*energy_step
     840              :          floquet_env%a_k(i_E) = -SUM(floquet_env%w0(:)* &
     841              :                                      AIMAG(z_one/(energy + gaussi*broad/2.0_dp - &
     842         3238 :                                                   floquet_env%eigenvalues(:))))/pi
     843              :       END DO
     844              : 
     845              :       ! (2) m=0 band structure by weighted count
     846              :       cum = 0.0_dp
     847              :       j = 0
     848           18 :       DO i = 1, nao
     849           16 :          target_level = REAL(i, dp) - 0.5_dp
     850         1052 :          DO WHILE (cum < target_level .AND. j < n_f_size)
     851         1036 :             j = j + 1
     852         1036 :             cum = cum + floquet_env%w0(j)
     853              :          END DO
     854           16 :          floquet_env%m0_energies(i) = floquet_env%eigenvalues(j) - mu
     855           18 :          floquet_env%m0_weights(i) = floquet_env%w0(j)
     856              :       END DO
     857              : 
     858              :       ! (3) Fold the m=0 bands (already relative to the VBM) into the first Floquet Brillouin zone.
     859              :       floquet_env%quasi_energies(:) = floquet_env%m0_energies &
     860           18 :                                       - omega*REAL(CEILING(floquet_env%m0_energies/omega - 0.5_dp), dp)
     861              : 
     862              :       ! Sort the quasi-energies
     863            6 :       ALLOCATE (work(nao))
     864            2 :       CALL sort(floquet_env%quasi_energies, nao, work)
     865            2 :       DEALLOCATE (work)
     866              : 
     867              :       ! Store this result on the subgroup source only, so the global sum below picks up
     868              :       ! each work item only once
     869            2 :       IF (para_env_sub%is_source()) THEN
     870            9 :          floquet_env%all_quasi_energies(:, ispin, ikp) = floquet_env%quasi_energies(:)
     871            3 :          floquet_env%all_a_k(:, ispin, ikp) = floquet_env%a_k(:)
     872            9 :          floquet_env%all_m0_energies(:, ispin, ikp) = floquet_env%m0_energies(:)
     873            9 :          floquet_env%all_m0_weights(:, ispin, ikp) = floquet_env%m0_weights(:)
     874              :       END IF
     875              : 
     876            2 :       CALL timestop(handle)
     877              : 
     878            2 :    END SUBROUTINE calculate_floquet_observables
     879              : 
     880              : ! **************************************************************************************************
     881              : !> \brief Print the Floquet header with the input parameters and a short description of the output.
     882              : !> \param bs_env ...
     883              : ! **************************************************************************************************
     884            2 :    SUBROUTINE write_floquet_header(bs_env)
     885              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     886              : 
     887              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'write_floquet_header'
     888              : 
     889              :       INTEGER                                            :: handle, n_f_size, unit_nr
     890              : 
     891            2 :       CALL timeset(routineN, handle)
     892              : 
     893            2 :       unit_nr = bs_env%unit_nr
     894            2 :       n_f_size = bs_env%n_ao*(1 + 2*bs_env%max_floquet_index)
     895              : 
     896            2 :       IF (unit_nr > 0) THEN
     897              : 
     898            1 :          WRITE (unit_nr, '(T2,A)') ' '
     899            1 :          WRITE (unit_nr, '(T2,A)') REPEAT('-', 79)
     900            1 :          WRITE (unit_nr, '(T2,A,A78)') '-', '-'
     901            1 :          WRITE (unit_nr, '(T2,A,A51,A27)') '-', 'FLOQUET BANDSTRUCTURE CALCULATION', '-'
     902            1 :          WRITE (unit_nr, '(T2,A,A78)') '-', '-'
     903            1 :          WRITE (unit_nr, '(T2,A)') REPEAT('-', 79)
     904            1 :          WRITE (unit_nr, '(T2,A)') ' '
     905              : 
     906            1 :          WRITE (unit_nr, '(T2,A,T37,A,T67,ES12.2E2)') "FLOQUET PARAMETERS", "Amplitude [V/m]:", &
     907            2 :             bs_env%floquet_amplitude*evolt/a_bohr
     908            1 :          WRITE (unit_nr, '(T37,A,T67,F12.4)') "Frequency [eV]:", bs_env%floquet_omega*evolt
     909            1 :          WRITE (unit_nr, '(T37,A)') REPEAT("-", 44)
     910            4 :          WRITE (unit_nr, '(T37,A,T55,3F8.4)') "Polarisation:", bs_env%floquet_polarisation(1:3)
     911            4 :          WRITE (unit_nr, '(T37,A,T55,3F8.4)') "Phase offsets:", pi*bs_env%floquet_phi(1:3)
     912            1 :          WRITE (unit_nr, '(T37,A)') REPEAT("-", 44)
     913            1 :          WRITE (unit_nr, '(T37,A,T67,I12)') "Max Floquet index:", bs_env%max_floquet_index
     914            1 :          WRITE (unit_nr, '(T37,A,T67,I12)') "Floquet Hamiltonian Size:", n_f_size
     915            1 :          WRITE (unit_nr, '(T37,A)') REPEAT("-", 44)
     916              :          WRITE (unit_nr, '(T37,A,T67,F12.4)') &
     917            1 :             "Energy window [eV]:", bs_env%energy_window_floquet*evolt
     918            1 :          WRITE (unit_nr, '(T37,A,T67,F12.4)') "Energy step [eV]:", bs_env%energy_step_floquet*evolt
     919            1 :          WRITE (unit_nr, '(T37,A,T67,F12.4)') "Broadening [eV]:", bs_env%broadening_floquet*evolt
     920            1 :          WRITE (unit_nr, '(T37,A)') REPEAT("-", 44)
     921            1 :          WRITE (unit_nr, '(A)') ""
     922              : 
     923              :          WRITE (unit_nr, '(T2,A)') &
     924            1 :             "We construct the Floquet-Bloch Hamiltonian and diagonalise it. Projecting the"
     925              :          WRITE (unit_nr, '(T2,A)') &
     926            1 :             "eigenvectors onto the Floquet sectors gives the weights w = Σ_n |<n,m|α>|²,"
     927              :          WRITE (unit_nr, '(T2,A)') &
     928            1 :             "from which all of the following are obtained."
     929            1 :          WRITE (unit_nr, '(A)') ""
     930              :          WRITE (unit_nr, '(T2,A)') &
     931            1 :             "The m=0 bands are the eigenvectors with the largest central-sector weight; they"
     932              :          WRITE (unit_nr, '(T2,A)') &
     933            1 :             "reduce to the equilibrium bands at zero field and are stored, with their"
     934              :          WRITE (unit_nr, '(T2,A)') &
     935            1 :             " weights, in FLOQUET_BANDSTRUCTURE.bs"
     936            1 :          WRITE (unit_nr, '(A)') ""
     937              :          WRITE (unit_nr, '(T2,A)') &
     938            1 :             "Folding those bands into the first Floquet Brillouin zone, relative to the VBM,"
     939              :          WRITE (unit_nr, '(T2,A)') &
     940            1 :             "gives the quasi-energies stored in QUASI_ENERGIES.bs"
     941            1 :          WRITE (unit_nr, '(A)') ""
     942              :          WRITE (unit_nr, '(T2,A)') &
     943            1 :             "The k-resolved density of states is obtained by computing the trace of"
     944              :          WRITE (unit_nr, '(T2,A)') &
     945            1 :             "the retarded Green's function and stored in FLOQUET_DOS.out"
     946              :          WRITE (unit_nr, '(T2,A)') &
     947            1 :             "DOS(ω,k) = -1/π*Im[Tr_KS(G^R(ω,k))]"
     948            1 :          WRITE (unit_nr, '(A)') ""
     949              :       END IF
     950              : 
     951            2 :       CALL timestop(handle)
     952              : 
     953            2 :    END SUBROUTINE write_floquet_header
     954              : 
     955              : ! **************************************************************************************************
     956              : !> \brief Sum the accumulated results across MPI ranks and write the m=0 band structure,
     957              : !>        the quasi-energies and the Floquet DOS to their files.
     958              : !> \param bs_env ...
     959              : !> \param floquet_env ...
     960              : ! **************************************************************************************************
     961            2 :    SUBROUTINE write_floquet_results(bs_env, floquet_env)
     962              :       TYPE(post_scf_bandstructure_type), POINTER         :: bs_env
     963              :       TYPE(floquet_env_type), INTENT(INOUT)              :: floquet_env
     964              : 
     965              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'write_floquet_results'
     966              : 
     967              :       CHARACTER(LEN=default_string_length)               :: fname
     968              :       INTEGER                                            :: bunit, handle, i, i_E, ikp_for_file, &
     969              :                                                             ispin, n_E, n_spin, nao, nkp_only_bs, &
     970              :                                                             nkp_start, qunit, wunit
     971              :       REAL(KIND=dp)                                      :: E_min, energy, energy_step, f_occ, kT, &
     972              :                                                             mu, x
     973              :       REAL(KIND=dp), DIMENSION(3)                        :: xkp
     974              : 
     975            2 :       CALL timeset(routineN, handle)
     976              : 
     977              :       ! Collect all results on the global communicator and write them
     978            2 :       CALL bs_env%para_env%sum(floquet_env%all_quasi_energies)
     979            2 :       CALL bs_env%para_env%sum(floquet_env%all_a_k)
     980            2 :       CALL bs_env%para_env%sum(floquet_env%all_m0_energies)
     981            2 :       CALL bs_env%para_env%sum(floquet_env%all_m0_weights)
     982              : 
     983            2 :       IF (bs_env%para_env%is_source()) THEN
     984              : 
     985            1 :          nao = floquet_env%nao
     986            1 :          n_spin = floquet_env%n_spin
     987            1 :          nkp_only_bs = floquet_env%nkp_only_bs
     988            1 :          n_E = floquet_env%n_E
     989            1 :          nkp_start = bs_env%nkp_only_DOS
     990              : 
     991              :          ! All three share one energy origin, the VBM.
     992            1 :          mu = floquet_reference_energy(bs_env)
     993            1 :          energy_step = bs_env%energy_step_floquet
     994            1 :          E_min = mu - bs_env%energy_window_floquet
     995              : 
     996              :          ! k_B T in Hartree (only used when TEMPERATURE > 0); E[K] = E[Hartree]*kelvin.
     997            1 :          kT = bs_env%floquet_temperature/kelvin
     998              : 
     999              :          ! Each file is opened once (REPLACE) and written for every band-structure k-point and spin.
    1000              : 
    1001              :          ! The m=0 band structure
    1002            1 :          WRITE (fname, "(2A)") TRIM(bs_env%floquet_bs_file), ".bs"
    1003            1 :          CALL open_file(TRIM(fname), unit_number=bunit, file_status="REPLACE", file_action="WRITE")
    1004            1 :          WRITE (bunit, "(A)") "# Floquet m=0 (central-sector) band structure"
    1005            1 :          WRITE (bunit, "(A)") "# (in units of eV, relative to the VBM, not folded)"
    1006            1 :          WRITE (bunit, "(A)") "# w = sum_n |<n,m=0|alpha>|^2 in [0,1]: w~1 clean m=0 replica,"
    1007            1 :          WRITE (bunit, "(A)") "# w~0.5 hybridised with a sideband (drive near resonance)"
    1008              : 
    1009              :          ! Quasi-energies
    1010            1 :          WRITE (fname, "(2A)") TRIM(bs_env%floquet_qe_file), ".bs"
    1011            1 :          CALL open_file(TRIM(fname), unit_number=qunit, file_status="REPLACE", file_action="WRITE")
    1012            1 :          WRITE (qunit, "(A)") "# Quasi-energies obtained by diagonalising the Floquet Hamiltonian"
    1013            1 :          WRITE (qunit, "(A)") "# (in units of eV, the m=0 bands relative to the VBM, folded to"
    1014            1 :          WRITE (qunit, "(A)") "# the first Floquet Brillouin zone -hbar*Omega/2 < e <= hbar*Omega/2)"
    1015              : 
    1016              :          ! DOS
    1017            1 :          WRITE (fname, "(2A)") TRIM(bs_env%floquet_dos_file), ".out"
    1018            1 :          CALL open_file(TRIM(fname), unit_number=wunit, file_status="REPLACE", file_action="WRITE")
    1019            1 :          WRITE (wunit, "(A)") "# Floquet Density of States: D(ω,k) = -1/π*Im[Tr_KS(G^R(ω,k))]"
    1020              : 
    1021            2 :          DO ikp_for_file = 1, nkp_only_bs
    1022            4 :             xkp(1:3) = bs_env%kpoints_DOS%xkp(1:3, nkp_start + ikp_for_file)
    1023            3 :             DO ispin = 1, n_spin
    1024              : 
    1025              :                ! <floquet_bs_file>.bs the m=0 band structure (absolute energies) and its weights
    1026              :                WRITE (bunit, "(A,I0,T10,A,I0,A,T24,3(1X,F14.8))") &
    1027            1 :                   "#  Spin ", ispin, "  Point ", ikp_for_file, ": ", xkp(1:3)
    1028            1 :                WRITE (bunit, "(A)") "#  Floquet band  Energy [eV]         m=0 weight"
    1029            9 :                DO i = 1, nao
    1030            8 :                   WRITE (bunit, "(I8,F21.8,F17.5)") i, &
    1031            8 :                      floquet_env%all_m0_energies(i, ispin, ikp_for_file)*evolt, &
    1032           17 :                      floquet_env%all_m0_weights(i, ispin, ikp_for_file)
    1033              :                END DO
    1034              : 
    1035              :                ! <floquet_qe_file>.bs Quasi-energies (folded to -ħΩ/2 < ε ≤ ħΩ/2)
    1036              :                WRITE (qunit, "(A,I0,T10,A,I0,A,T24,3(1X,F14.8))") &
    1037            1 :                   "#  Spin ", ispin, "  Point ", ikp_for_file, ": ", xkp(1:3)
    1038            1 :                WRITE (qunit, "(A)") "#  Floquet band  Quasi-energy [eV]"
    1039            9 :                DO i = 1, nao
    1040            8 :                   WRITE (qunit, "(I8,F21.8)") i, &
    1041           17 :                      floquet_env%all_quasi_energies(i, ispin, ikp_for_file)*evolt
    1042              :                END DO
    1043              : 
    1044              :                ! <floquet_dos_file>.out the k-resolved DOS A(k,ω), and (if TEMPERATURE > 0) the
    1045              :                ! occupied spectral weight f(E)*A(k,ω), f the Fermi-Dirac occupation at the VBM.
    1046              :                WRITE (wunit, "(A,I0,T10,A,I0,A,T24,3(1X,F14.8))") &
    1047            1 :                   "#  Spin ", ispin, "  Point ", ikp_for_file, ": ", xkp(1:3)
    1048            2 :                IF (bs_env%floquet_temperature > 0.0_dp) THEN
    1049            1 :                   WRITE (wunit, "(A)") "#Energy-VBM (eV)  A(ω,k) = DOS (1/eV)   f*A = occupied DOS (1/eV)"
    1050            3 :                   DO i_E = 1, n_E
    1051            2 :                      energy = E_min + i_E*energy_step
    1052            2 :                      x = (energy - mu)/kT                    ! (E - VBM)/k_B T, dimensionless
    1053            2 :                      IF (x > 40.0_dp) THEN                   ! guard EXP overflow at low T
    1054              :                         f_occ = 0.0_dp
    1055            2 :                      ELSE IF (x < -40.0_dp) THEN
    1056              :                         f_occ = 1.0_dp
    1057              :                      ELSE
    1058            2 :                         f_occ = 1.0_dp/(EXP(x) + 1.0_dp)
    1059              :                      END IF
    1060            2 :                      WRITE (wunit, "(2X,3G13.4)") (energy - mu)*evolt, &
    1061            2 :                         floquet_env%all_a_k(i_E, ispin, ikp_for_file)/evolt, &
    1062            5 :                         f_occ*floquet_env%all_a_k(i_E, ispin, ikp_for_file)/evolt
    1063              :                   END DO
    1064              :                ELSE
    1065            0 :                   WRITE (wunit, "(A)") "#Energy-VBM (eV)  A(ω,k) = DOS (1/eV)"
    1066            0 :                   DO i_E = 1, n_E
    1067            0 :                      energy = E_min + i_E*energy_step
    1068            0 :                      WRITE (wunit, "(2X,2G13.4)") (energy - mu)*evolt, &
    1069            0 :                         floquet_env%all_a_k(i_E, ispin, ikp_for_file)/evolt
    1070              :                   END DO
    1071              :                END IF
    1072              : 
    1073              :             END DO
    1074              :          END DO
    1075              : 
    1076            1 :          CALL close_file(bunit)
    1077            1 :          CALL close_file(qunit)
    1078            1 :          CALL close_file(wunit)
    1079              :       END IF
    1080              : 
    1081            2 :       CALL timestop(handle)
    1082              : 
    1083            2 :    END SUBROUTINE write_floquet_results
    1084              : 
    1085              : END MODULE floquet_utils
        

Generated by: LCOV version 2.0-1