LCOV - code coverage report
Current view: top level - src - bse_properties.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:936074a) Lines: 95.6 % 411 393
Test Date: 2025-12-04 06:27:48 Functions: 80.0 % 5 4

            Line data    Source code
       1              : !--------------------------------------------------------------------------------------------------!
       2              : !   CP2K: A general program to perform molecular dynamics simulations                              !
       3              : !   Copyright 2000-2025 CP2K developers group <https://cp2k.org>                                   !
       4              : !                                                                                                  !
       5              : !   SPDX-License-Identifier: GPL-2.0-or-later                                                      !
       6              : !--------------------------------------------------------------------------------------------------!
       7              : 
       8              : ! **************************************************************************************************
       9              : !> \brief Routines for computing excitonic properties, e.g. exciton diameter, from the BSE
      10              : !> \par History
      11              : !>      10.2024 created [Maximilian Graml]
      12              : ! **************************************************************************************************
      13              : MODULE bse_properties
      14              :    USE bse_util,                        ONLY: fm_general_add_bse,&
      15              :                                               print_bse_nto_cubes,&
      16              :                                               reshuffle_eigvec,&
      17              :                                               trace_exciton_descr
      18              :    USE cp_files,                        ONLY: close_file,&
      19              :                                               open_file
      20              :    USE cp_fm_basic_linalg,              ONLY: cp_fm_scale_and_add,&
      21              :                                               cp_fm_trace
      22              :    USE cp_fm_diag,                      ONLY: cp_fm_svd
      23              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      24              :                                               cp_fm_struct_release,&
      25              :                                               cp_fm_struct_type
      26              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      27              :                                               cp_fm_get_submatrix,&
      28              :                                               cp_fm_release,&
      29              :                                               cp_fm_set_all,&
      30              :                                               cp_fm_to_fm,&
      31              :                                               cp_fm_to_fm_submat,&
      32              :                                               cp_fm_to_fm_submat_general,&
      33              :                                               cp_fm_type
      34              :    USE cp_log_handling,                 ONLY: cp_get_default_logger,&
      35              :                                               cp_logger_get_default_unit_nr,&
      36              :                                               cp_logger_type
      37              :    USE input_section_types,             ONLY: section_vals_get_subs_vals,&
      38              :                                               section_vals_type,&
      39              :                                               section_vals_val_get
      40              :    USE kinds,                           ONLY: dp
      41              :    USE mathconstants,                   ONLY: pi
      42              :    USE mp2_types,                       ONLY: mp2_type
      43              :    USE parallel_gemm_api,               ONLY: parallel_gemm
      44              :    USE physcon,                         ONLY: c_light_au,&
      45              :                                               evolt
      46              :    USE qs_environment_types,            ONLY: get_qs_env,&
      47              :                                               qs_environment_type
      48              :    USE qs_mo_types,                     ONLY: allocate_mo_set,&
      49              :                                               deallocate_mo_set,&
      50              :                                               init_mo_set,&
      51              :                                               mo_set_type
      52              : #include "./base/base_uses.f90"
      53              : 
      54              :    IMPLICIT NONE
      55              : 
      56              :    PRIVATE
      57              : 
      58              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'bse_properties'
      59              : 
      60              :    PUBLIC :: exciton_descr_type
      61              : 
      62              :    PUBLIC :: get_exciton_descriptors, get_oscillator_strengths, compute_and_print_absorption_spectrum, &
      63              :              calculate_NTOs
      64              : 
      65              : ! TYPE definitions for exciton wavefunction descriptors
      66              : 
      67              :    TYPE exciton_descr_type
      68              :       REAL(KIND=dp), DIMENSION(3)                        :: r_e = 0.0_dp, &
      69              :                                                             r_h = 0.0_dp, &
      70              :                                                             r_e_sq = 0.0_dp, &
      71              :                                                             r_h_sq = 0.0_dp, &
      72              :                                                             r_e_shift = 0.0_dp, &
      73              :                                                             r_h_shift = 0.0_dp, &
      74              :                                                             d_eh_dir = 0.0_dp, &
      75              :                                                             sigma_e_dir = 0.0_dp, &
      76              :                                                             sigma_h_dir = 0.0_dp, &
      77              :                                                             d_exc_dir = 0.0_dp
      78              :       REAL(KIND=dp), DIMENSION(3, 3)                      :: r_e_h = 0.0_dp, &
      79              :                                                              cov_e_h = 0.0_dp, &
      80              :                                                              corr_e_h_matrix = 0.0_dp
      81              :       REAL(KIND=dp)                                      :: sigma_e = 0.0_dp, &
      82              :                                                             sigma_h = 0.0_dp, &
      83              :                                                             cov_e_h_sum = 0.0_dp, &
      84              :                                                             corr_e_h = 0.0_dp, &
      85              :                                                             diff_r_abs = 0.0_dp, &
      86              :                                                             diff_r_sqr = 0.0_dp, &
      87              :                                                             norm_XpY = 0.0_dp
      88              :       LOGICAL                                           :: flag_TDA = .FALSE.
      89              :    END TYPE exciton_descr_type
      90              : 
      91              : CONTAINS
      92              : 
      93              : ! **************************************************************************************************
      94              : !> \brief Compute and return BSE dipoles d_r^n = sqrt(2) sum_ia < ψ_i | r | ψ_a > ( X_ia^n + Y_ia^n )
      95              : !>    and oscillator strengths f^n = 2/3 * Ω^n sum_r∈(x,y,z) ( d_r^n )^2
      96              : !>    Prelim Ref.: Eqs. (23), (24)
      97              : !>    in J. Chem. Phys. 152, 044105 (2020); https://doi.org/10.1063/1.5123290
      98              : !> \param fm_eigvec_X ...
      99              : !> \param Exc_ens ...
     100              : !> \param fm_dipole_ai_trunc ...
     101              : !> \param trans_mom_bse BSE dipole vectors in real space per excitation level
     102              : !> \param oscill_str Oscillator strength per excitation level
     103              : !> \param polarizability_residues Residues of polarizability ("tensorial oscillator strength")
     104              : !>          per excitation level
     105              : !> \param mp2_env ...
     106              : !> \param homo_red ...
     107              : !> \param virtual_red ...
     108              : !> \param unit_nr ...
     109              : !> \param fm_eigvec_Y ...
     110              : ! **************************************************************************************************
     111           30 :    SUBROUTINE get_oscillator_strengths(fm_eigvec_X, Exc_ens, fm_dipole_ai_trunc, &
     112              :                                        trans_mom_bse, oscill_str, polarizability_residues, &
     113              :                                        mp2_env, homo_red, virtual_red, unit_nr, &
     114              :                                        fm_eigvec_Y)
     115              : 
     116              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_eigvec_X
     117              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     118              :          INTENT(IN)                                      :: Exc_ens
     119              :       TYPE(cp_fm_type), DIMENSION(3)                     :: fm_dipole_ai_trunc
     120              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
     121              :          INTENT(OUT)                                     :: trans_mom_bse
     122              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     123              :          INTENT(OUT)                                     :: oscill_str
     124              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
     125              :          INTENT(OUT)                                     :: polarizability_residues
     126              :       TYPE(mp2_type), INTENT(IN)                         :: mp2_env
     127              :       INTEGER, INTENT(IN)                                :: homo_red, virtual_red, unit_nr
     128              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_eigvec_Y
     129              : 
     130              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_oscillator_strengths'
     131              : 
     132              :       INTEGER                                            :: handle, idir, jdir, n
     133              :       TYPE(cp_fm_struct_type), POINTER :: fm_struct_dipole_MO_trunc_reordered, &
     134              :          fm_struct_trans_mom_bse
     135              :       TYPE(cp_fm_type)                                   :: fm_eigvec_XYsum
     136          120 :       TYPE(cp_fm_type), DIMENSION(3)                     :: fm_dipole_MO_trunc_reordered, &
     137          240 :                                                             fm_dipole_per_dir, fm_trans_mom_bse
     138              : 
     139           30 :       CALL timeset(routineN, handle)
     140              : 
     141              :       CALL cp_fm_struct_create(fm_struct_dipole_MO_trunc_reordered, fm_eigvec_X%matrix_struct%para_env, &
     142           30 :                                fm_eigvec_X%matrix_struct%context, 1, homo_red*virtual_red)
     143              :       CALL cp_fm_struct_create(fm_struct_trans_mom_bse, fm_eigvec_X%matrix_struct%para_env, &
     144           30 :                                fm_eigvec_X%matrix_struct%context, 1, homo_red*virtual_red)
     145              : 
     146              :       ! Include excitonic amplitudes in dipoles, i.e. obtain "BSE dipoles":
     147              :       ! \vec{D}_n = sqrt(2) * sum_{i,a} \vec{D}_ai (X_{ai}^{(n)} + Y_{ai}^{(n)})
     148              : 
     149              :       ! Reorder dipoles in order to execute the sum over i and a by parallel gemm
     150          120 :       DO idir = 1, 3
     151              :          CALL cp_fm_create(fm_dipole_MO_trunc_reordered(idir), matrix_struct=fm_struct_dipole_MO_trunc_reordered, &
     152           90 :                            name="dipoles_mo_reordered")
     153           90 :          CALL cp_fm_set_all(fm_dipole_MO_trunc_reordered(idir), 0.0_dp)
     154              :          CALL fm_general_add_bse(fm_dipole_MO_trunc_reordered(idir), fm_dipole_ai_trunc(idir), 1.0_dp, &
     155              :                                  1, 1, &
     156              :                                  1, virtual_red, &
     157           90 :                                  unit_nr, [2, 4, 3, 1], mp2_env)
     158          120 :          CALL cp_fm_release(fm_dipole_per_dir(idir))
     159              :       END DO
     160              : 
     161          120 :       DO idir = 1, 3
     162              :          CALL cp_fm_create(fm_trans_mom_bse(idir), matrix_struct=fm_struct_trans_mom_bse, &
     163           90 :                            name="excitonic_dipoles")
     164          120 :          CALL cp_fm_set_all(fm_trans_mom_bse(idir), 0.0_dp)
     165              :       END DO
     166              : 
     167              :       ! If TDA is invoked, Y is not present as it is simply 0
     168           30 :       CALL cp_fm_create(fm_eigvec_XYsum, matrix_struct=fm_eigvec_X%matrix_struct, name="excit_amplitude_sum")
     169           30 :       CALL cp_fm_set_all(fm_eigvec_XYsum, 0.0_dp)
     170           30 :       CALL cp_fm_to_fm(fm_eigvec_X, fm_eigvec_XYsum)
     171           30 :       IF (PRESENT(fm_eigvec_Y)) THEN
     172           18 :          CALL cp_fm_scale_and_add(1.0_dp, fm_eigvec_XYsum, 1.0_dp, fm_eigvec_Y)
     173              :       END IF
     174          120 :       DO idir = 1, 3
     175              :          CALL parallel_gemm('N', 'N', 1, homo_red*virtual_red, homo_red*virtual_red, SQRT(2.0_dp), &
     176          120 :                             fm_dipole_MO_trunc_reordered(idir), fm_eigvec_XYsum, 0.0_dp, fm_trans_mom_bse(idir))
     177              :       END DO
     178              : 
     179              :       ! Get oscillator strengths themselves
     180           90 :       ALLOCATE (oscill_str(homo_red*virtual_red))
     181              :       ! trans_mom_bse needs to be a 2D array per direction idir, such that cp_fm_get_submatrix can directly
     182              :       !  write to it
     183           90 :       ALLOCATE (trans_mom_bse(3, 1, homo_red*virtual_red))
     184           90 :       ALLOCATE (polarizability_residues(3, 3, homo_red*virtual_red))
     185         6830 :       trans_mom_bse(:, :, :) = 0.0_dp
     186              : 
     187              :       ! Sum over all directions
     188          120 :       DO idir = 1, 3
     189          120 :          CALL cp_fm_get_submatrix(fm_trans_mom_bse(idir), trans_mom_bse(idir, :, :))
     190              :       END DO
     191              : 
     192         1390 :       DO n = 1, homo_red*virtual_red
     193         5440 :          DO idir = 1, 3
     194        17680 :             DO jdir = 1, 3
     195        16320 :                polarizability_residues(idir, jdir, n) = 2.0_dp*Exc_ens(n)*trans_mom_bse(idir, 1, n)*trans_mom_bse(jdir, 1, n)
     196              :             END DO
     197              :          END DO
     198         5470 :          oscill_str(n) = 2.0_dp/3.0_dp*Exc_ens(n)*SUM(ABS(trans_mom_bse(:, 1, n))**2)
     199              :       END DO
     200              : 
     201           30 :       CALL cp_fm_struct_release(fm_struct_dipole_MO_trunc_reordered)
     202           30 :       CALL cp_fm_struct_release(fm_struct_trans_mom_bse)
     203          120 :       DO idir = 1, 3
     204           90 :          CALL cp_fm_release(fm_dipole_MO_trunc_reordered(idir))
     205           90 :          CALL cp_fm_release(fm_trans_mom_bse(idir))
     206          120 :          CALL cp_fm_release(fm_dipole_ai_trunc(idir))
     207              :       END DO
     208           30 :       CALL cp_fm_release(fm_eigvec_XYsum)
     209              : 
     210           30 :       CALL timestop(handle)
     211              : 
     212           90 :    END SUBROUTINE get_oscillator_strengths
     213              : 
     214              : ! **************************************************************************************************
     215              : !> \brief Computes and returns absorption spectrum for the frequency range and broadening
     216              : !>    provided by the user.
     217              : !>    Prelim Ref.: C. Ullrich, Time-Dependent Density-Functional Theory: Concepts and Applications
     218              : !>    (Oxford University Press, Oxford, 2012), Eq. 7.51
     219              : !> \param oscill_str ...
     220              : !> \param polarizability_residues ...
     221              : !> \param Exc_ens ...
     222              : !> \param info_approximation ...
     223              : !> \param unit_nr ...
     224              : !> \param mp2_env ...
     225              : ! **************************************************************************************************
     226            2 :    SUBROUTINE compute_and_print_absorption_spectrum(oscill_str, polarizability_residues, Exc_ens, &
     227              :                                                     info_approximation, unit_nr, mp2_env)
     228              : 
     229              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     230              :          INTENT(IN)                                      :: oscill_str
     231              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :), &
     232              :          INTENT(IN)                                      :: polarizability_residues
     233              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:), &
     234              :          INTENT(IN)                                      :: Exc_ens
     235              :       CHARACTER(LEN=10)                                  :: info_approximation
     236              :       INTEGER, INTENT(IN)                                :: unit_nr
     237              :       TYPE(mp2_type), INTENT(INOUT)                      :: mp2_env
     238              : 
     239              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'compute_and_print_absorption_spectrum'
     240              : 
     241              :       CHARACTER(LEN=10)                                  :: eta_str, width_eta_format_str
     242              :       CHARACTER(LEN=40)                                  :: file_name_crosssection, &
     243              :                                                             file_name_spectrum
     244              :       INTEGER                                            :: handle, i, idir, j, jdir, k, num_steps, &
     245              :                                                             unit_nr_file, width_eta
     246              :       REAL(KIND=dp)                                      :: eta, freq_end, freq_start, freq_step, &
     247              :                                                             omega
     248            2 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :)        :: abs_cross_section, abs_spectrum
     249            2 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: eta_list
     250              :       TYPE(cp_logger_type), POINTER                      :: logger
     251              : 
     252            2 :       CALL timeset(routineN, handle)
     253              : 
     254            2 :       freq_step = mp2_env%bse%bse_spectrum_freq_step_size
     255            2 :       freq_start = mp2_env%bse%bse_spectrum_freq_start
     256            2 :       freq_end = mp2_env%bse%bse_spectrum_freq_end
     257            2 :       eta_list => mp2_env%bse%bse_eta_spectrum_list
     258              : 
     259              :       ! Calculate number of steps to fit given frequency range
     260            2 :       num_steps = NINT((freq_end - freq_start)/freq_step) + 1
     261              : 
     262            4 :       DO k = 1, SIZE(eta_list)
     263            2 :          eta = eta_list(k)
     264              : 
     265              :          ! Some magic to get a nice formatting of the eta value in filenames
     266            2 :          width_eta = MAX(1, INT(LOG10(eta)) + 1) + 4
     267            2 :          WRITE (width_eta_format_str, "(A2,I0,A3)") '(F', width_eta, '.3)'
     268            2 :          WRITE (eta_str, width_eta_format_str) eta*evolt
     269              :          ! Filename itself
     270            2 :          file_name_spectrum = 'BSE'//TRIM(ADJUSTL(info_approximation))//'eta='//TRIM(eta_str)//'.spectrum'
     271            2 :          file_name_crosssection = 'BSE'//TRIM(ADJUSTL(info_approximation))//'eta='//TRIM(eta_str)//'.crosssection'
     272              : 
     273              :          ! First column is frequency in eV, second column is imaginary part of the trace of the polarizability
     274              :          ! The following 9 columns are the entries of the polarizability tensor
     275            6 :          ALLOCATE (abs_spectrum(num_steps, 11))
     276        22046 :          abs_spectrum(:, :) = 0.0_dp
     277              :          ! Also calculate and print the photoabsorption cross section tensor
     278              :          ! σ_{µ µ'}(ω) = 4πω Im{α_{µ µ'}(ω)} / c
     279            4 :          ALLOCATE (abs_cross_section(num_steps, 11))
     280        22046 :          abs_cross_section(:, :) = 0.0_dp
     281              : 
     282              :          ! Calculate the imaginary part of the mean dipole polarizability α_{avg}(ω)
     283              :          ! which is given by (cf. C. Ullrichs Book on TDDFT, Eq. 7.51)
     284              :          ! We introduce an additional - due to his convention for charge vs particle density, see also:
     285              :          ! Computer Physics Communications, 208:149–161, November 2016
     286              :          ! https://doi.org/10.1016/j.cpc.2016.06.019
     287              :          ! α_{avg}(ω) = - \sum_{n=1}^{N_exc} \frac{f_n}{(ω+iη)² - (Ω^n)²}
     288              :          ! and then the imaginary part is (in the limit η -> 0)
     289              :          ! Im[α_{avg}(ω)] = - \sum_{n=1}^{N_exc} f_n * η / ((ω - Ω^n)² + η²)
     290              :          ! where f_n are the oscillator strengths and E_exc the excitation energies
     291              :          ! For the full polarizability tensor, we have
     292              :          ! α_{µ µ'}(ω) = - \sum_n [2 Ω^n d^n_µ d^n_µ'] / [(ω+iη)^2- (Ω^n)^2]
     293              :          !             = - \sum_n "polarizability_residues" / [(ω+iη)^2- (Ω^n)^2]
     294         2004 :          DO i = 1, num_steps
     295         2002 :             omega = freq_start + (i - 1)*freq_step
     296         2002 :             abs_spectrum(i, 1) = omega
     297        98100 :             DO j = 1, SIZE(oscill_str)
     298              :                abs_spectrum(i, 2) = abs_spectrum(i, 2) - oscill_str(j)* &
     299        96096 :                                     AIMAG(1/((omega + CMPLX(0.0, eta, kind=dp))**2 - Exc_ens(j)**2))
     300       386386 :                DO idir = 1, 3
     301      1249248 :                   DO jdir = 1, 3
     302              :                      ! Factor 2 from formula for tensor is already in the polarizability_residues
     303              :                      !  to follow the same convention as the oscillator strengths
     304              :                      abs_spectrum(i, 2 + (idir - 1)*3 + jdir) = abs_spectrum(i, 2 + (idir - 1)*3 + jdir) &
     305              :                                                                 - polarizability_residues(idir, jdir, j)* &
     306      1153152 :                                                                 AIMAG(1/((omega + CMPLX(0.0, eta, kind=dp))**2 - Exc_ens(j)**2))
     307              :                   END DO
     308              :                END DO
     309              :             END DO
     310              :          END DO
     311              : 
     312              :          ! Extract cross section σ from polarizability tensor
     313         2004 :          DO i = 1, num_steps
     314         2002 :             omega = abs_spectrum(i, 1)
     315         2002 :             abs_cross_section(i, 1) = omega
     316        22024 :             abs_cross_section(i, 2:) = 4.0_dp*pi*abs_spectrum(i, 2:)*omega/c_light_au
     317              :          END DO
     318              : 
     319              :          !For debug runs: Export an entry of the two tensors to allow regtests on spectra
     320            2 :          IF (mp2_env%bse%bse_debug_print) THEN
     321            2 :             IF (unit_nr > 0) THEN
     322            1 :                WRITE (unit_nr, '(T2,A10,T13,A,T65,F16.4)') 'BSE|DEBUG|', &
     323            1 :                   'Averaged dynamical dipole polarizability at 8.2 eV:', &
     324            2 :                   abs_spectrum(83, 2)
     325            1 :                WRITE (unit_nr, '(T2,A10,T13,A,T65,F16.4)') 'BSE|DEBUG|', &
     326            1 :                   'Averaged photoabsorption cross section at 8.2 eV:', &
     327            2 :                   abs_cross_section(83, 2)
     328              :             END IF
     329              :          END IF
     330              : 
     331              :          ! Print it to file
     332            2 :          logger => cp_get_default_logger()
     333            2 :          IF (logger%para_env%is_source()) THEN
     334            2 :             unit_nr_file = cp_logger_get_default_unit_nr()
     335              :          ELSE
     336            0 :             unit_nr_file = -1
     337              :          END IF
     338              : 
     339            2 :          IF (unit_nr_file > 0) THEN
     340              :             CALL open_file(file_name_crosssection, unit_number=unit_nr_file, &
     341            2 :                            file_status="UNKNOWN", file_action="WRITE")
     342              :             WRITE (unit_nr_file, '(A,A6)') "# Photoabsorption cross section  σ_{µ µ'}(ω) =  -4πω/c * Im[ \sum_n "// &
     343            2 :                "[2 Ω^n d_µ^n d_µ'^n] / [(ω+iη)²- (Ω^n)²] ] from Bethe Salpeter equation for method ", &
     344            4 :                TRIM(ADJUSTL(info_approximation))
     345            2 :             WRITE (unit_nr_file, '(A20,1X,10(2X,A20,1X))') "#     Frequency (eV)", "σ_{avg}(ω)", "σ_xx(ω)", &
     346            2 :                "σ_xy(ω)", "σ_xz(ω)", "σ_yx(ω)", "σ_yy(ω)", "σ_yz(ω)", "σ_zx(ω)", &
     347            4 :                "σ_zy(ω)", "σ_zz(ω)"
     348         2004 :             DO i = 1, num_steps
     349         2004 :                WRITE (unit_nr_file, '(11(F20.8,1X))') abs_cross_section(i, 1)*evolt, abs_cross_section(i, 2:11)
     350              :             END DO
     351            2 :             CALL close_file(unit_nr_file)
     352              :          END IF
     353            2 :          DEALLOCATE (abs_cross_section)
     354              : 
     355            2 :          IF (unit_nr_file > 0) THEN
     356              :             CALL open_file(file_name_spectrum, unit_number=unit_nr_file, &
     357            2 :                            file_status="UNKNOWN", file_action="WRITE")
     358              :             WRITE (unit_nr_file, '(A,A6)') "# Imaginary part of polarizability α_{µ µ'}(ω) = -\sum_n "// &
     359            2 :                "[2 Ω^n d_µ^n d_µ'^n] / [(ω+iη)²- (Ω^n)²] from Bethe Salpeter equation for method ", &
     360            4 :                TRIM(ADJUSTL(info_approximation))
     361            2 :             WRITE (unit_nr_file, '(A20,1X,10(2X,A20,1X))') "#     Frequency (eV)", "Im{α_{avg}(ω)}", "Im{α_xx(ω)}", &
     362            2 :                "Im{α_xy(ω)}", "Im{α_xz(ω)}", "Im{α_yx(ω)}", "Im{α_yy(ω)}", "Im{α_yz(ω)}", "Im{α_zx(ω)}", &
     363            4 :                "Im{α_zy(ω)}", "Im{α_zz(ω)}"
     364         2004 :             DO i = 1, num_steps
     365         2004 :                WRITE (unit_nr_file, '(11(F20.8,1X))') abs_spectrum(i, 1)*evolt, abs_spectrum(i, 2:11)
     366              :             END DO
     367            2 :             CALL close_file(unit_nr_file)
     368              :          END IF
     369            4 :          DEALLOCATE (abs_spectrum)
     370              :       END DO
     371              : 
     372            2 :       IF (unit_nr > 0) THEN
     373            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     374              :          WRITE (unit_nr, '(T2,A4,T7,A,A)') &
     375            1 :             'BSE|', "Printed optical absorption spectrum to local files, e.g. "
     376              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     377            1 :             'BSE|', file_name_spectrum
     378              :          WRITE (unit_nr, '(T2,A4,T7,A,A)') &
     379            1 :             'BSE|', "as well as photoabsorption cross section to, e.g. "
     380              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     381            1 :             'BSE|', file_name_crosssection
     382              :          WRITE (unit_nr, '(T2,A4,T7,A52)') &
     383            1 :             'BSE|', "using the Eq. (7.51) from C. Ullrichs Book on TDDFT:"
     384            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     385              :          WRITE (unit_nr, '(T2,A4,T10,A75)') &
     386            1 :             'BSE|', "Im{α_{avg}(ω)} = -Im{\sum_{n=1}^{N_exc} \frac{f_n}{(ω+iη)² - (Ω^n)²}}"
     387            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     388              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     389            1 :             'BSE|', "or for the full polarizability tensor:"
     390              :          WRITE (unit_nr, '(T2,A4,T10,A)') &
     391            1 :             'BSE|', "α_{µ µ'}(ω) =  -\sum_n [2 Ω^n d_µ^n d_µ'^n] / [(ω+iη)²- (Ω^n)²]"
     392            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     393              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     394            1 :             'BSE|', "as well as Eq. (7.48):"
     395              :          WRITE (unit_nr, '(T2,A4,T10,A)') &
     396            1 :             'BSE|', "σ_{µ µ'}(ω) = 4πω Im{α_{µ µ'}(ω)} / c"
     397            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     398              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     399            1 :             'BSE|', "with transition moments d_µ^n, oscillator strengths f_n,"
     400              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     401            1 :             'BSE|', "excitation energies Ω^n and the speed of light c."
     402            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     403              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     404            1 :             'BSE|', "Please note that we adopt an additional minus sign for both quantities,"
     405              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     406            1 :             'BSE|', "due to the convention for charge vs particle density as done in MolGW:"
     407              :          WRITE (unit_nr, '(T2,A4,T7,A)') &
     408            1 :             'BSE|', "https://doi.org/10.1016/j.cpc.2016.06.019."
     409            1 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     410              :       END IF
     411              : 
     412            2 :       CALL timestop(handle)
     413              : 
     414            4 :    END SUBROUTINE compute_and_print_absorption_spectrum
     415              : 
     416              : ! **************************************************************************************************
     417              : !> \brief ...
     418              : !> \param fm_X ...
     419              : !> \param fm_Y ...
     420              : !> \param mo_coeff ...
     421              : !> \param homo ...
     422              : !> \param virtual ...
     423              : !> \param info_approximation ...
     424              : !> \param oscill_str ...
     425              : !> \param qs_env ...
     426              : !> \param unit_nr ...
     427              : !> \param mp2_env ...
     428              : ! **************************************************************************************************
     429            4 :    SUBROUTINE calculate_NTOs(fm_X, fm_Y, &
     430            4 :                              mo_coeff, homo, virtual, &
     431              :                              info_approximation, &
     432              :                              oscill_str, &
     433              :                              qs_env, unit_nr, mp2_env)
     434              : 
     435              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_X
     436              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_Y
     437              :       TYPE(cp_fm_type), DIMENSION(:), INTENT(IN)         :: mo_coeff
     438              :       INTEGER, INTENT(IN)                                :: homo, virtual
     439              :       CHARACTER(LEN=10)                                  :: info_approximation
     440              :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: oscill_str
     441              :       TYPE(qs_environment_type), POINTER                 :: qs_env
     442              :       INTEGER, INTENT(IN)                                :: unit_nr
     443              :       TYPE(mp2_type), INTENT(INOUT)                      :: mp2_env
     444              : 
     445              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'calculate_NTOs'
     446              : 
     447              :       CHARACTER(LEN=20), DIMENSION(2)                    :: nto_name
     448              :       INTEGER                                            :: handle, homo_irred, i, i_nto, info_svd, &
     449              :                                                             j, n_exc, n_nto, nao_full, nao_trunc
     450            4 :       INTEGER, DIMENSION(:), POINTER                     :: stride
     451              :       LOGICAL                                            :: append_cube, cube_file
     452            4 :       REAL(KIND=dp), ALLOCATABLE, DIMENSION(:)           :: eigval_svd_squ
     453            4 :       REAL(KIND=dp), DIMENSION(:), POINTER               :: eigval_svd
     454              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_m, fm_struct_mo_coeff, &
     455              :                                                             fm_struct_nto_holes, &
     456              :                                                             fm_struct_nto_particles, &
     457              :                                                             fm_struct_nto_set
     458              :       TYPE(cp_fm_type) :: fm_eigvl, fm_eigvr_t, fm_m, fm_mo_coeff, fm_nto_coeff_holes, &
     459              :          fm_nto_coeff_particles, fm_nto_set, fm_X_ia, fm_Y_ai
     460            4 :       TYPE(mo_set_type), ALLOCATABLE, DIMENSION(:)       :: nto_set
     461              :       TYPE(section_vals_type), POINTER                   :: bse_section, input, nto_section
     462              : 
     463            4 :       CALL timeset(routineN, handle)
     464              :       CALL get_qs_env(qs_env=qs_env, &
     465            4 :                       input=input)
     466            4 :       bse_section => section_vals_get_subs_vals(input, "DFT%XC%WF_CORRELATION%RI_RPA%GW%BSE")
     467              : 
     468            4 :       nao_full = qs_env%mos(1)%nao
     469            4 :       nao_trunc = homo + virtual
     470              :       ! This is not influenced by the BSE cutoff
     471            4 :       homo_irred = qs_env%mos(1)%homo
     472              :       ! M will have a block structure and is quadratic in homo+virtual, i.e.
     473              :       !                          occ   virt
     474              :       !                       |   0    X_i,a |  occ  = homo
     475              :       !     M        =        | Y_a,i    0   |  virt = virtual
     476              :       !
     477              :       ! X and Y are here not the eigenvectors X_ia,n - instead we fix n and reshape the combined ia index
     478              :       ! Notice the index structure of the lower block, i.e. X is transposed
     479              :       CALL cp_fm_struct_create(fm_struct_m, &
     480              :                                fm_X%matrix_struct%para_env, fm_X%matrix_struct%context, &
     481            4 :                                nao_trunc, nao_trunc)
     482              :       CALL cp_fm_struct_create(fm_struct_mo_coeff, &
     483              :                                fm_X%matrix_struct%para_env, fm_X%matrix_struct%context, &
     484            4 :                                nao_full, nao_trunc)
     485              :       CALL cp_fm_struct_create(fm_struct_nto_holes, &
     486              :                                fm_X%matrix_struct%para_env, fm_X%matrix_struct%context, &
     487            4 :                                nao_full, nao_trunc)
     488              :       CALL cp_fm_struct_create(fm_struct_nto_particles, &
     489              :                                fm_X%matrix_struct%para_env, fm_X%matrix_struct%context, &
     490            4 :                                nao_full, nao_trunc)
     491              : 
     492              :       CALL cp_fm_create(fm_mo_coeff, matrix_struct=fm_struct_mo_coeff, &
     493            4 :                         name="mo_coeff")
     494              :       ! Here, we take care of possible cutoffs
     495              :       ! Simply truncating the matrix causes problems with the print function
     496              :       ! Therefore, we keep the dimension, but set the coefficients of truncated indices to 0
     497              :       CALL cp_fm_to_fm_submat_general(mo_coeff(1), fm_mo_coeff, &
     498              :                                       nao_full, nao_trunc, &
     499              :                                       1, homo_irred - homo + 1, &
     500              :                                       1, 1, &
     501            4 :                                       mo_coeff(1)%matrix_struct%context)
     502              : 
     503              :       ! Print some information about the NTOs
     504            4 :       IF (unit_nr > 0) THEN
     505            2 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
     506            4 :             'The Natural Transition Orbital (NTO) pairs φ_I(r_e) and χ_I(r_h) for a fixed'
     507            2 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
     508            4 :             'excitation index n are obtained by singular value decomposition of T'
     509            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     510            2 :          WRITE (unit_nr, '(T2,A4,T15,A)') 'BSE|', &
     511            4 :             '        = (0   X)'
     512            2 :          IF (PRESENT(fm_Y)) THEN
     513            1 :             WRITE (unit_nr, '(T2,A4,T15,A)') 'BSE|', &
     514            2 :                'T       = (Y^T 0)'
     515              :          ELSE
     516            1 :             WRITE (unit_nr, '(T2,A4,T15,A)') 'BSE|', &
     517            2 :                'T       = (0   0)'
     518              :          END IF
     519            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     520            2 :          WRITE (unit_nr, '(T2,A4,T15,A)') 'BSE|', &
     521            4 :             'T        = U Λ V^T'
     522            2 :          WRITE (unit_nr, '(T2,A4,T15,A)') 'BSE|', &
     523            4 :             'φ_I(r_e) = \sum_p V_pI ψ_p(r_e)'
     524            2 :          WRITE (unit_nr, '(T2,A4,T15,A)') 'BSE|', &
     525            4 :             'χ_I(r_h) = \sum_p U_pI ψ_p(r_e)'
     526            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     527            2 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
     528            4 :             'where we have introduced'
     529            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     530              :          WRITE (unit_nr, '(T2,A4,T7,A,T20,A)') &
     531            2 :             'BSE|', "ψ_p:", "occupied and virtual molecular orbitals,"
     532              :          WRITE (unit_nr, '(T2,A4,T7,A,T20,A)') &
     533            2 :             'BSE|', "φ_I(r_e):", "NTO state for the electron,"
     534              :          WRITE (unit_nr, '(T2,A4,T7,A,T20,A)') &
     535            2 :             'BSE|', "χ_I(r_h):", "NTO state for the hole,"
     536              :          WRITE (unit_nr, '(T2,A4,T7,A,T20,A)') &
     537            2 :             'BSE|', "Λ:", "diagonal matrix of NTO weights λ_I,"
     538            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     539            2 :          WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
     540            4 :             "The NTOs are calculated with the following settings:"
     541            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     542            2 :          WRITE (unit_nr, '(T2,A4,T7,A,T71,I10)') 'BSE|', 'Number of excitations, for which NTOs are computed', &
     543            4 :             mp2_env%bse%num_print_exc_ntos
     544            2 :          IF (mp2_env%bse%eps_nto_osc_str > 0.0_dp) THEN
     545            0 :             WRITE (unit_nr, '(T2,A4,T7,A,T71,F10.3)') 'BSE|', 'Threshold for oscillator strength f^n', &
     546            0 :                mp2_env%bse%eps_nto_osc_str
     547              :          ELSE
     548            2 :             WRITE (unit_nr, '(T2,A4,T7,A,T71,A10)') 'BSE|', 'Threshold for oscillator strength f^n', &
     549            4 :                ADJUSTL("---")
     550              :          END IF
     551            2 :          WRITE (unit_nr, '(T2,A4,T7,A,T72,F10.3)') 'BSE|', 'Threshold for NTO weights (λ_I)^2', &
     552            4 :             mp2_env%bse%eps_nto_eigval
     553              :       END IF
     554              : 
     555              :       ! Write the header of NTO info table
     556            4 :       IF (unit_nr > 0) THEN
     557            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     558            2 :          IF (.NOT. PRESENT(fm_Y)) THEN
     559            1 :             WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
     560            2 :                'NTOs from solving the BSE within the TDA:'
     561              :          ELSE
     562            1 :             WRITE (unit_nr, '(T2,A4,T7,A)') 'BSE|', &
     563            2 :                'NTOs from solving the BSE without the TDA:'
     564              :          END IF
     565            2 :          WRITE (unit_nr, '(T2,A4)') 'BSE|'
     566            2 :          WRITE (unit_nr, '(T2,A4,T8,A12,T22,A8,T33,A14,T62,A)') 'BSE|', &
     567            4 :             'Excitation n', "TDA/ABBA", "Index of NTO I", 'NTO weights (λ_I)^2'
     568              :       END IF
     569              : 
     570          104 :       DO j = 1, mp2_env%bse%num_print_exc_ntos
     571          100 :          n_exc = mp2_env%bse%bse_nto_state_list_final(j)
     572              :          ! Takes care of unallocated oscill_str array in case of Triplet
     573          100 :          IF (mp2_env%bse%eps_nto_osc_str > 0.0_dp) THEN
     574              :             ! Check actual values
     575            0 :             IF (oscill_str(n_exc) < mp2_env%bse%eps_nto_osc_str) THEN
     576              :                ! Print skipped levels to table
     577            0 :                IF (unit_nr > 0) THEN
     578            0 :                   WRITE (unit_nr, '(T2,A4)') 'BSE|'
     579            0 :                   WRITE (unit_nr, '(T2,A4,T8,I12,T24,A6,T42,A39)') 'BSE|', &
     580            0 :                      n_exc, info_approximation, "Skipped (Oscillator strength too small)"
     581              :                END IF
     582              :                CYCLE
     583              :             END IF
     584              :          END IF
     585              : 
     586              :          CALL cp_fm_create(fm_m, matrix_struct=fm_struct_m, &
     587          100 :                            name="single_part_trans_dm")
     588          100 :          CALL cp_fm_set_all(fm_m, 0.0_dp)
     589              : 
     590              :          CALL cp_fm_create(fm_nto_coeff_holes, matrix_struct=fm_struct_nto_holes, &
     591          100 :                            name="nto_coeffs_holes")
     592          100 :          CALL cp_fm_set_all(fm_nto_coeff_holes, 0.0_dp)
     593              : 
     594              :          CALL cp_fm_create(fm_nto_coeff_particles, matrix_struct=fm_struct_nto_particles, &
     595          100 :                            name="nto_coeffs_particles")
     596          100 :          CALL cp_fm_set_all(fm_nto_coeff_particles, 0.0_dp)
     597              : 
     598              :          ! Reshuffle from X_ia,n_exc to X_i,a
     599              :          CALL reshuffle_eigvec(fm_X, fm_X_ia, homo, virtual, n_exc, &
     600          100 :                                .FALSE., unit_nr, mp2_env)
     601              : 
     602              :          ! Copy X to upper block in M, i.e. starting from column homo+1
     603              :          CALL cp_fm_to_fm_submat(fm_X_ia, fm_m, &
     604              :                                  homo, virtual, &
     605              :                                  1, 1, &
     606          100 :                                  1, homo + 1)
     607          100 :          CALL cp_fm_release(fm_X_ia)
     608              :          ! Copy Y if present
     609          100 :          IF (PRESENT(fm_Y)) THEN
     610              :             ! Reshuffle from Y_ia,n_exc to Y_a,i
     611              :             CALL reshuffle_eigvec(fm_Y, fm_Y_ai, homo, virtual, n_exc, &
     612           50 :                                   .TRUE., unit_nr, mp2_env)
     613              : 
     614              :             ! Copy Y^T to lower block in M, i.e. starting from row homo+1
     615              :             CALL cp_fm_to_fm_submat(fm_Y_ai, fm_m, &
     616              :                                     virtual, homo, &
     617              :                                     1, 1, &
     618           50 :                                     homo + 1, 1)
     619              : 
     620           50 :             CALL cp_fm_release(fm_Y_ai)
     621              : 
     622              :          END IF
     623              : 
     624              :          ! Now we compute the SVD of M_{occ+virt,occ+virt}, which yields
     625              :          ! M = U * Lambda * V^T
     626              :          ! Initialize matrices and arrays to store left/right eigenvectors and singular values
     627              :          CALL cp_fm_create(matrix=fm_eigvl, &
     628              :                            matrix_struct=fm_m%matrix_struct, &
     629          100 :                            name="LEFT_SINGULAR_MATRIX")
     630          100 :          CALL cp_fm_set_all(fm_eigvl, alpha=0.0_dp)
     631              :          CALL cp_fm_create(matrix=fm_eigvr_t, &
     632              :                            matrix_struct=fm_m%matrix_struct, &
     633          100 :                            name="RIGHT_SINGULAR_MATRIX")
     634          100 :          CALL cp_fm_set_all(fm_eigvr_t, alpha=0.0_dp)
     635              : 
     636          300 :          ALLOCATE (eigval_svd(nao_trunc))
     637         1700 :          eigval_svd(:) = 0.0_dp
     638              :          info_svd = 0
     639          100 :          CALL cp_fm_svd(fm_m, fm_eigvl, fm_eigvr_t, eigval_svd, info_svd)
     640          604 :          IF (info_svd /= 0) THEN
     641            0 :             IF (unit_nr > 0) THEN
     642              :                CALL cp_warn(__LOCATION__, &
     643              :                             "SVD for computation of NTOs not successful. "// &
     644            0 :                             "Skipping print of NTOs.")
     645            0 :                IF (info_svd > 0) THEN
     646              :                   CALL cp_warn(__LOCATION__, &
     647              :                                "PDGESVD detected heterogeneity. "// &
     648            0 :                                "Decreasing number of MPI ranks might solve this issue.")
     649              :                END IF
     650              :             END IF
     651              :             ! Release matrices to avoid memory leaks
     652            0 :             CALL cp_fm_release(fm_m)
     653            0 :             CALL cp_fm_release(fm_nto_coeff_holes)
     654            0 :             CALL cp_fm_release(fm_nto_coeff_particles)
     655              :          ELSE
     656              :             ! Rescale singular values as done in Martin2003 (10.1063/1.1558471)
     657          200 :             ALLOCATE (eigval_svd_squ(nao_trunc))
     658         1700 :             eigval_svd_squ(:) = eigval_svd(:)**2
     659              :             ! Sanity check for TDA: In case of TDA, the sum should be \sum_ia |X_ia|^2 = 1
     660          100 :             IF (.NOT. PRESENT(fm_Y)) THEN
     661          850 :                IF (ABS(SUM(eigval_svd_squ) - 1) >= 1e-5) THEN
     662            0 :                   CPWARN("Sum of NTO coefficients deviates from 1!")
     663              :                END IF
     664              :             END IF
     665              : 
     666              :             ! Create NTO coefficients for later print to grid via TDDFT routine
     667              :             ! Apply U = fm_eigvl to MO coeffs, which yields hole states
     668              :             CALL parallel_gemm("N", "N", nao_full, nao_trunc, nao_trunc, 1.0_dp, fm_mo_coeff, fm_eigvl, 0.0_dp, &
     669          100 :                                fm_nto_coeff_holes)
     670              : 
     671              :             ! Apply V^T = fm_eigvr_t to MO coeffs, which yields particle states
     672              :             CALL parallel_gemm("N", "T", nao_full, nao_trunc, nao_trunc, 1.0_dp, fm_mo_coeff, fm_eigvr_t, 0.0_dp, &
     673          100 :                                fm_nto_coeff_particles)
     674              : 
     675              :             !Release intermediary work matrices
     676          100 :             CALL cp_fm_release(fm_m)
     677          100 :             CALL cp_fm_release(fm_eigvl)
     678          100 :             CALL cp_fm_release(fm_eigvr_t)
     679              : 
     680              :             ! Transfer NTO coefficients to sets
     681          100 :             nto_name(1) = 'Hole_coord'
     682          100 :             nto_name(2) = 'Particle_coord'
     683          300 :             ALLOCATE (nto_set(2))
     684              :             ! Extract number of significant NTOs
     685          100 :             n_nto = 0
     686          320 :             DO i_nto = 1, nao_trunc
     687          320 :                IF (eigval_svd_squ(i_nto) > mp2_env%bse%eps_nto_eigval) THEN
     688          220 :                   n_nto = n_nto + 1
     689              :                ELSE
     690              :                   ! Since svd orders in descending order, we can exit the loop if smaller
     691              :                   EXIT
     692              :                END IF
     693              :             END DO
     694              : 
     695          100 :             IF (unit_nr > 0) THEN
     696           50 :                WRITE (unit_nr, '(T2,A4)') 'BSE|'
     697          160 :                DO i_nto = 1, n_nto
     698          110 :                   WRITE (unit_nr, '(T2,A4,T8,I12,T24,A6,T41,I6,T71,F10.5)') 'BSE|', &
     699          270 :                      n_exc, info_approximation, i_nto, eigval_svd_squ(i_nto)
     700              :                END DO
     701              :             END IF
     702              : 
     703              :             CALL cp_fm_struct_create(fm_struct_nto_set, template_fmstruct=fm_struct_nto_holes, &
     704          100 :                                      ncol_global=n_nto)
     705          100 :             CALL cp_fm_create(fm_nto_set, fm_struct_nto_set)
     706          300 :             DO i = 1, 2
     707          200 :                CALL allocate_mo_set(nto_set(i), nao_trunc, n_nto, 0, 0.0_dp, 2.0_dp, 0.0_dp)
     708          300 :                CALL init_mo_set(nto_set(i), fm_ref=fm_nto_set, name=nto_name(i))
     709              :             END DO
     710          100 :             CALL cp_fm_release(fm_nto_set)
     711          100 :             CALL cp_fm_struct_release(fm_struct_nto_set)
     712              : 
     713              :             ! Fill NTO sets
     714          100 :             CALL cp_fm_to_fm(fm_nto_coeff_holes, nto_set(1)%mo_coeff, ncol=n_nto)
     715          100 :             CALL cp_fm_to_fm(fm_nto_coeff_particles, nto_set(2)%mo_coeff, ncol=n_nto)
     716              : 
     717              :             ! Cube files
     718          100 :             nto_section => section_vals_get_subs_vals(bse_section, "NTO_ANALYSIS")
     719          100 :             CALL section_vals_val_get(nto_section, "CUBE_FILES", l_val=cube_file)
     720          100 :             CALL section_vals_val_get(nto_section, "STRIDE", i_vals=stride)
     721          100 :             CALL section_vals_val_get(nto_section, "APPEND", l_val=append_cube)
     722          100 :             IF (cube_file) THEN
     723              :                CALL print_bse_nto_cubes(qs_env, nto_set, n_exc, info_approximation, &
     724            0 :                                         stride, append_cube, nto_section)
     725              :             END IF
     726              : 
     727          100 :             CALL cp_fm_release(fm_nto_coeff_holes)
     728          100 :             CALL cp_fm_release(fm_nto_coeff_particles)
     729          100 :             DEALLOCATE (eigval_svd)
     730          100 :             DEALLOCATE (eigval_svd_squ)
     731          300 :             DO i = 1, 2
     732          300 :                CALL deallocate_mo_set(nto_set(i))
     733              :             END DO
     734          400 :             DEALLOCATE (nto_set)
     735              :          END IF
     736              :       END DO
     737              : 
     738            4 :       CALL cp_fm_release(fm_mo_coeff)
     739            4 :       CALL cp_fm_struct_release(fm_struct_m)
     740            4 :       CALL cp_fm_struct_release(fm_struct_nto_holes)
     741            4 :       CALL cp_fm_struct_release(fm_struct_nto_particles)
     742            4 :       CALL cp_fm_struct_release(fm_struct_mo_coeff)
     743              : 
     744            4 :       CALL timestop(handle)
     745              : 
     746            8 :    END SUBROUTINE calculate_NTOs
     747              : 
     748              : ! **************************************************************************************************
     749              : !> \brief ...
     750              : !> \param exc_descr Allocated and initialized on exit
     751              : !> \param fm_X_ia ...
     752              : !> \param fm_multipole_ij_trunc ...
     753              : !> \param fm_multipole_ab_trunc ...
     754              : !> \param fm_multipole_ai_trunc ...
     755              : !> \param i_exc ...
     756              : !> \param homo ...
     757              : !> \param virtual ...
     758              : !> \param fm_Y_ia ...
     759              : ! **************************************************************************************************
     760          110 :    SUBROUTINE get_exciton_descriptors(exc_descr, fm_X_ia, &
     761              :                                       fm_multipole_ij_trunc, fm_multipole_ab_trunc, &
     762              :                                       fm_multipole_ai_trunc, &
     763              :                                       i_exc, homo, virtual, &
     764              :                                       fm_Y_ia)
     765              : 
     766              :       TYPE(exciton_descr_type), ALLOCATABLE, &
     767              :          DIMENSION(:)                                    :: exc_descr
     768              :       TYPE(cp_fm_type), INTENT(IN)                       :: fm_X_ia
     769              :       TYPE(cp_fm_type), ALLOCATABLE, DIMENSION(:), &
     770              :          INTENT(IN)                                      :: fm_multipole_ij_trunc, &
     771              :                                                             fm_multipole_ab_trunc, &
     772              :                                                             fm_multipole_ai_trunc
     773              :       INTEGER, INTENT(IN)                                :: i_exc, homo, virtual
     774              :       TYPE(cp_fm_type), INTENT(IN), OPTIONAL             :: fm_Y_ia
     775              : 
     776              :       CHARACTER(LEN=*), PARAMETER :: routineN = 'get_exciton_descriptors'
     777              : 
     778              :       INTEGER                                            :: handle, i_dir, j_dir
     779              :       INTEGER, DIMENSION(3)                              :: mask_quadrupole
     780              :       LOGICAL                                            :: flag_TDA
     781              :       REAL(KIND=dp)                                      :: norm_X, norm_XpY, norm_Y
     782              :       REAL(KIND=dp), DIMENSION(3)                        :: r_e_sq_X, r_e_sq_Y, r_e_X, r_e_Y, &
     783              :                                                             r_h_sq_X, r_h_sq_Y, r_h_X, r_h_Y
     784              :       REAL(KIND=dp), DIMENSION(3, 3)                     :: r_e_h_XX, r_e_h_XY, r_e_h_YX, r_e_h_YY
     785              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_ab, fm_struct_ia
     786              :       TYPE(cp_fm_type)                                   :: fm_work_ba, fm_work_ia, fm_work_ia_2
     787              : 
     788          110 :       CALL timeset(routineN, handle)
     789          110 :       IF (PRESENT(fm_Y_ia)) THEN
     790              :          flag_TDA = .FALSE.
     791              :       ELSE
     792           60 :          flag_TDA = .TRUE.
     793              :       END IF
     794              : 
     795              :       ! translates 1,2,3 to diagonal entries of quadrupoles xx, yy, zz
     796              :       ! Ordering in quadrupole moments is x, y, z, xx, xy, xz, yy, yz, zz
     797          110 :       mask_quadrupole = [4, 7, 9]
     798              : 
     799              :       CALL cp_fm_struct_create(fm_struct_ia, &
     800          110 :                                context=fm_X_ia%matrix_struct%context, nrow_global=homo, ncol_global=virtual)
     801              :       CALL cp_fm_struct_create(fm_struct_ab, &
     802          110 :                                context=fm_X_ia%matrix_struct%context, nrow_global=virtual, ncol_global=virtual)
     803              : 
     804          110 :       r_e_X(:) = 0.0_dp
     805          110 :       r_e_Y(:) = 0.0_dp
     806          110 :       r_h_X(:) = 0.0_dp
     807          110 :       r_h_Y(:) = 0.0_dp
     808          110 :       r_e_sq_X(:) = 0.0_dp
     809          110 :       r_h_sq_X(:) = 0.0_dp
     810          110 :       r_e_sq_Y(:) = 0.0_dp
     811          110 :       r_h_sq_Y(:) = 0.0_dp
     812          110 :       r_e_h_XX(:, :) = 0.0_dp
     813          110 :       r_e_h_XY(:, :) = 0.0_dp
     814          110 :       r_e_h_YX(:, :) = 0.0_dp
     815          110 :       r_e_h_YY(:, :) = 0.0_dp
     816              : 
     817          110 :       norm_X = 0.0_dp
     818          110 :       norm_Y = 0.0_dp
     819          110 :       norm_XpY = 0.0_dp
     820              : 
     821              :       ! Initialize values of exciton descriptors
     822          440 :       exc_descr(i_exc)%r_e(:) = 0.0_dp
     823          440 :       exc_descr(i_exc)%r_h(:) = 0.0_dp
     824          440 :       exc_descr(i_exc)%r_e_sq(:) = 0.0_dp
     825          440 :       exc_descr(i_exc)%r_h_sq(:) = 0.0_dp
     826         1430 :       exc_descr(i_exc)%r_e_h(:, :) = 0.0_dp
     827              : 
     828          110 :       exc_descr(i_exc)%flag_TDA = flag_TDA
     829          110 :       exc_descr(i_exc)%norm_XpY = 0.0_dp
     830              : 
     831              :       ! Norm of X
     832          110 :       CALL cp_fm_trace(fm_X_ia, fm_X_ia, norm_X)
     833          110 :       norm_XpY = norm_X
     834              :       ! Norm of Y
     835          110 :       IF (.NOT. flag_TDA) THEN
     836           50 :          CALL cp_fm_trace(fm_Y_ia, fm_Y_ia, norm_Y)
     837           50 :          norm_XpY = norm_XpY + norm_Y
     838              :       END IF
     839              : 
     840          110 :       exc_descr(i_exc)%norm_XpY = norm_XpY
     841              : 
     842              :       ! <r_h>_X = Tr[ X^T µ_ij X + Y µ_ab Y^T ] = X_ai µ_ij X_ja + Y_ia  µ_ab Y_bi
     843          440 :       DO i_dir = 1, 3
     844              :          ! <r_h>_X = X_ai µ_ij X_ja + ...
     845          330 :          CALL trace_exciton_descr(fm_X_ia, fm_multipole_ij_trunc(i_dir), fm_X_ia, r_h_X(i_dir))
     846          330 :          r_h_X(i_dir) = r_h_X(i_dir)/norm_XpY
     847          440 :          IF (.NOT. flag_TDA) THEN
     848              :             ! <r_h>_X = ... + Y_ia  µ_ab Y_bi
     849          150 :             CALL trace_exciton_descr(fm_Y_ia, fm_Y_ia, fm_multipole_ab_trunc(i_dir), r_h_Y(i_dir))
     850          150 :             r_h_Y(i_dir) = r_h_Y(i_dir)/norm_XpY
     851              :          END IF
     852              :       END DO
     853          440 :       exc_descr(i_exc)%r_h(:) = r_h_X(:) + r_h_Y(:)
     854              : 
     855              :       ! <r_e>_X = Tr[ X µ_ab X^T + Y^T µ_ij Y ] = X_ia µ_ab X_bi + Y_ai µ_ij Y_ja
     856          440 :       DO i_dir = 1, 3
     857              :          ! <r_e>_X = work_ib X_bi + ... = X_ib^T work_ib + ...
     858          330 :          CALL trace_exciton_descr(fm_X_ia, fm_X_ia, fm_multipole_ab_trunc(i_dir), r_e_X(i_dir))
     859          330 :          r_e_X(i_dir) = r_e_X(i_dir)/norm_XpY
     860          440 :          IF (.NOT. flag_TDA) THEN
     861              :             ! <r_e>_X = ... + Y_ai µ_ij Y_ja
     862          150 :             CALL trace_exciton_descr(fm_Y_ia, fm_multipole_ij_trunc(i_dir), fm_Y_ia, r_e_Y(i_dir))
     863          150 :             r_e_Y(i_dir) = r_e_Y(i_dir)/norm_XpY
     864              :          END IF
     865              :       END DO
     866          440 :       exc_descr(i_exc)%r_e(:) = r_e_X(:) + r_e_Y(:)
     867              : 
     868              :       ! <r_h^2>_X = Tr[ X^T M_ij X + Y M_ab Y^T ] = X_ai M_ij X_ja + Y_ia  M_ab Y_bi
     869          440 :       DO i_dir = 1, 3
     870              :          ! <r_h^2>_X = X_ai M_ij X_ja + ...
     871              :          CALL trace_exciton_descr(fm_X_ia, fm_multipole_ij_trunc(mask_quadrupole(i_dir)), &
     872          330 :                                   fm_X_ia, r_h_sq_X(i_dir))
     873          330 :          r_h_sq_X(i_dir) = r_h_sq_X(i_dir)/norm_XpY
     874          440 :          IF (.NOT. flag_TDA) THEN
     875              :             ! <r_h^2>_X = ... + Y_ia  M_ab Y_bi
     876              :             CALL trace_exciton_descr(fm_Y_ia, fm_Y_ia, &
     877          150 :                                      fm_multipole_ab_trunc(mask_quadrupole(i_dir)), r_h_sq_Y(i_dir))
     878          150 :             r_h_sq_Y(i_dir) = r_h_sq_Y(i_dir)/norm_XpY
     879              :          END IF
     880              :       END DO
     881          440 :       exc_descr(i_exc)%r_h_sq(:) = r_h_sq_X(:) + r_h_sq_Y(:)
     882              : 
     883              :       ! <r_e^2>_X = Tr[ X M_ab X^T + Y^T M_ij Y ] = X_ia M_ab X_bi + Y_ai M_ij Y_ja
     884          440 :       DO i_dir = 1, 3
     885              :          ! <r_e^2>_X = work_ib X_bi + ... = X_ib^T work_ib + ...
     886              :          CALL trace_exciton_descr(fm_X_ia, fm_X_ia, &
     887          330 :                                   fm_multipole_ab_trunc(mask_quadrupole(i_dir)), r_e_sq_X(i_dir))
     888          330 :          r_e_sq_X(i_dir) = r_e_sq_X(i_dir)/norm_XpY
     889          440 :          IF (.NOT. flag_TDA) THEN
     890              :             ! <r_e^2>_X = ... + Y_ai M_ij Y_ja
     891              :             CALL trace_exciton_descr(fm_Y_ia, fm_multipole_ij_trunc(mask_quadrupole(i_dir)), &
     892          150 :                                      fm_Y_ia, r_e_sq_Y(i_dir))
     893          150 :             r_e_sq_Y(i_dir) = r_e_sq_Y(i_dir)/norm_XpY
     894              :          END IF
     895              :       END DO
     896          440 :       exc_descr(i_exc)%r_e_sq(:) = r_e_sq_X(:) + r_e_sq_Y(:)
     897              : 
     898              :       ! <r_e^\mu r_h^\mu'>_X
     899              :       !   = Tr[ X^T µ'_ij X µ_ab  +  Y^T µ_ij Y µ'_ab + X µ'_ai Y µ_ai + Y µ_ai X µ'_ai]
     900              :       !   = X_bj µ'_ji X_ia µ_ab + Y_bj µ_ji Y_ia µ'_ab + X_ia µ'_aj Y_jb µ_bi + Y_ia µ_aj X_jb µ'_bi
     901              :       ! The i_dir and j_dir convert to mu and mu'
     902          110 :       CALL cp_fm_create(fm_work_ia, fm_struct_ia)
     903          110 :       CALL cp_fm_create(fm_work_ia_2, fm_struct_ia)
     904          110 :       CALL cp_fm_create(fm_work_ba, fm_struct_ab)
     905          440 :       DO i_dir = 1, 3
     906         1430 :          DO j_dir = 1, 3
     907              :             ! First term - X^T µ'_ij X µ_ab
     908          990 :             CALL cp_fm_set_all(fm_work_ia, 0.0_dp)
     909          990 :             CALL cp_fm_set_all(fm_work_ia_2, 0.0_dp)
     910              :             ! work_ib = X_ia µ_ab
     911              :             CALL parallel_gemm("N", "N", homo, virtual, virtual, 1.0_dp, &
     912          990 :                                fm_X_ia, fm_multipole_ab_trunc(i_dir), 0.0_dp, fm_work_ia)
     913              :             ! work_ja_2 = µ'_ji work_ia
     914              :             CALL parallel_gemm("N", "N", homo, virtual, homo, 1.0_dp, &
     915          990 :                                fm_multipole_ij_trunc(j_dir), fm_work_ia, 0.0_dp, fm_work_ia_2)
     916              :             ! <r_e^\mu r_h^\mu'>_X = work_ia_2 X_bj + ... = X^T work_ia_2 + ...
     917          990 :             CALL cp_fm_trace(fm_X_ia, fm_work_ia_2, r_e_h_XX(i_dir, j_dir))
     918          990 :             r_e_h_XX(i_dir, j_dir) = r_e_h_XX(i_dir, j_dir)/norm_XpY
     919         1320 :             IF (.NOT. flag_TDA) THEN
     920              :                ! Second term -  Y^T µ_ij Y µ'_ab
     921          450 :                CALL cp_fm_set_all(fm_work_ia, 0.0_dp)
     922          450 :                CALL cp_fm_set_all(fm_work_ia_2, 0.0_dp)
     923              :                ! work_ib = Y_ia µ'_ab
     924              :                CALL parallel_gemm("N", "N", homo, virtual, virtual, 1.0_dp, &
     925          450 :                                   fm_Y_ia, fm_multipole_ab_trunc(j_dir), 0.0_dp, fm_work_ia)
     926              :                ! work_ja_2 = µ_ji work_ia
     927              :                CALL parallel_gemm("N", "N", homo, virtual, homo, 1.0_dp, &
     928          450 :                                   fm_multipole_ij_trunc(i_dir), fm_work_ia, 0.0_dp, fm_work_ia_2)
     929              :                ! <r_h r_e>_X = work_ia_2 Y_bj + ... = Y^T work_ia_2 + ...
     930          450 :                CALL cp_fm_trace(fm_Y_ia, fm_work_ia_2, r_e_h_YY(i_dir, j_dir))
     931          450 :                r_e_h_YY(i_dir, j_dir) = r_e_h_YY(i_dir, j_dir)/norm_XpY
     932              : 
     933              :                ! Third term - X µ'_ai Y µ_ai = X_ia µ'_aj Y_jb µ_bi
     934              :                !     Reshuffle for usage of trace (where first argument is transposed)
     935              :                !     = µ'_aj Y_jb µ_bi X_ia =
     936              :                !        \___________/
     937              :                !          fm_work_ai
     938              :                !     fm_work_ai = µ'_aj Y_jb µ_bi
     939              :                !     fm_work_ia = µ_ib Y_bj µ'_ja
     940              :                !                        \_____/
     941              :                !                       fm_work_ba
     942          450 :                CALL cp_fm_set_all(fm_work_ba, 0.0_dp)
     943          450 :                CALL cp_fm_set_all(fm_work_ia, 0.0_dp)
     944              :                ! fm_work_ba = Y_bj µ'_ja
     945              :                CALL parallel_gemm("T", "T", virtual, virtual, homo, 1.0_dp, &
     946          450 :                                   fm_Y_ia, fm_multipole_ai_trunc(j_dir), 0.0_dp, fm_work_ba)
     947              :                ! fm_work_ia = µ_ib fm_work_ba
     948              :                CALL parallel_gemm("T", "N", homo, virtual, virtual, 1.0_dp, &
     949          450 :                                   fm_multipole_ai_trunc(i_dir), fm_work_ba, 0.0_dp, fm_work_ia)
     950              :                ! <r_h r_e>_X = ... + X_ia µ_aj Y_jb µ_bi
     951          450 :                CALL cp_fm_trace(fm_work_ia, fm_X_ia, r_e_h_XY(i_dir, j_dir))
     952          450 :                r_e_h_XY(i_dir, j_dir) = r_e_h_XY(i_dir, j_dir)/norm_XpY
     953              : 
     954              :                ! Fourth term - Y µ_ai X µ'_ai = Y_ia µ_aj X_jb µ'_bi
     955              :                !     Reshuffle for usage of trace (where first argument is transposed)
     956              :                !     = µ_aj X_jb µ'_bi Y_ia =
     957              :                !        \___________/
     958              :                !         fm_work_ai
     959              :                !     fm_work_ai = µ_aj X_jb µ'_bi
     960              :                !     fm_work_ia = µ'_ib X_bj µ_ja
     961              :                !                         \_____/
     962              :                !                       fm_work_ba
     963          450 :                CALL cp_fm_set_all(fm_work_ba, 0.0_dp)
     964          450 :                CALL cp_fm_set_all(fm_work_ia, 0.0_dp)
     965              :                ! fm_work_ba = Y_bj µ_ja
     966              :                CALL parallel_gemm("T", "T", virtual, virtual, homo, 1.0_dp, &
     967          450 :                                   fm_X_ia, fm_multipole_ai_trunc(i_dir), 0.0_dp, fm_work_ba)
     968              :                ! fm_work_ia = µ'_ib fm_work_ba
     969              :                CALL parallel_gemm("T", "N", homo, virtual, virtual, 1.0_dp, &
     970          450 :                                   fm_multipole_ai_trunc(j_dir), fm_work_ba, 0.0_dp, fm_work_ia)
     971              :                ! <r_h r_e>_X = ... + X_ia µ_aj Y_jb µ_bi
     972          450 :                CALL cp_fm_trace(fm_work_ia, fm_Y_ia, r_e_h_YX(i_dir, j_dir))
     973          450 :                r_e_h_YX(i_dir, j_dir) = r_e_h_YX(i_dir, j_dir)/norm_XpY
     974              :             END IF
     975              :          END DO
     976              :       END DO
     977         1430 :       exc_descr(i_exc)%r_e_h(:, :) = r_e_h_XX(:, :) + r_e_h_XY(:, :) + r_e_h_YX(:, :) + r_e_h_YY(:, :)
     978              : 
     979          110 :       CALL cp_fm_release(fm_work_ia)
     980          110 :       CALL cp_fm_release(fm_work_ia_2)
     981          110 :       CALL cp_fm_release(fm_work_ba)
     982              : 
     983              :       ! Now we compute all the descriptors and correlation coefficients
     984              :       ! Order is: Directional ones, then covariances and correlation coefficients and
     985              : 
     986              :       ! diff_r_abs = |<r_h>_X - <r_e>_X|
     987          440 :       exc_descr(i_exc)%diff_r_abs = SQRT(SUM((exc_descr(i_exc)%r_h(:) - exc_descr(i_exc)%r_e(:))**2))
     988              : 
     989              :       ! σ_e = sqrt( <r_e^2>_X - <r_e>_X^2 )
     990          770 :       exc_descr(i_exc)%sigma_e = SQRT(SUM(exc_descr(i_exc)%r_e_sq(:)) - SUM(exc_descr(i_exc)%r_e(:)**2))
     991              : 
     992              :       ! σ_h = sqrt( <r_h^2>_X - <r_h>_X^2 )
     993          770 :       exc_descr(i_exc)%sigma_h = SQRT(SUM(exc_descr(i_exc)%r_h_sq(:)) - SUM(exc_descr(i_exc)%r_h(:)**2))
     994              : 
     995              :       ! Now directed ones
     996          440 :       DO i_dir = 1, 3
     997          330 :          exc_descr(i_exc)%d_eh_dir(i_dir) = ABS(exc_descr(i_exc)%r_h(i_dir) - exc_descr(i_exc)%r_e(i_dir))
     998          330 :          exc_descr(i_exc)%sigma_e_dir(i_dir) = SQRT(exc_descr(i_exc)%r_e_sq(i_dir) - exc_descr(i_exc)%r_e(i_dir)**2)
     999          440 :          exc_descr(i_exc)%sigma_h_dir(i_dir) = SQRT(exc_descr(i_exc)%r_h_sq(i_dir) - exc_descr(i_exc)%r_h(i_dir)**2)
    1000              :       END DO
    1001              : 
    1002              :       ! Covariance and correlation coefficient (as well as crosscorrelation matrices)
    1003              :       ! COV(r_e, r_h) = < r_e r_h >_X - < r_e >_X < r_h >_X
    1004          110 :       exc_descr(i_exc)%cov_e_h_sum = 0.0_dp
    1005         1430 :       exc_descr(i_exc)%cov_e_h(:, :) = 0.0_dp
    1006         1430 :       exc_descr(i_exc)%corr_e_h_matrix(:, :) = 0.0_dp
    1007          440 :       DO i_dir = 1, 3
    1008         1320 :          DO j_dir = 1, 3
    1009              :             exc_descr(i_exc)%cov_e_h(i_dir, j_dir) = exc_descr(i_exc)%r_e_h(i_dir, j_dir) &
    1010          990 :                                                      - exc_descr(i_exc)%r_e(i_dir)*exc_descr(i_exc)%r_h(j_dir)
    1011              :             exc_descr(i_exc)%corr_e_h_matrix(i_dir, j_dir) = &
    1012              :                exc_descr(i_exc)%cov_e_h(i_dir, j_dir)/ &
    1013         1320 :                (exc_descr(i_exc)%sigma_e_dir(i_dir)*exc_descr(i_exc)%sigma_h_dir(j_dir))
    1014              :          END DO
    1015              :          exc_descr(i_exc)%cov_e_h_sum = exc_descr(i_exc)%cov_e_h_sum + &
    1016              :                                         exc_descr(i_exc)%r_e_h(i_dir, i_dir) - &
    1017          440 :                                         exc_descr(i_exc)%r_e(i_dir)*exc_descr(i_exc)%r_h(i_dir)
    1018              :       END DO
    1019              : 
    1020              :       ! e-h-correlation coefficient R_eh = COV(r_e, r_h) / ( σ_e σ_h )
    1021          110 :       exc_descr(i_exc)%corr_e_h = exc_descr(i_exc)%cov_e_h_sum/(exc_descr(i_exc)%sigma_e*exc_descr(i_exc)%sigma_h)
    1022              : 
    1023              :       ! root-mean-square e-h separation
    1024              :       exc_descr(i_exc)%diff_r_sqr = SQRT(exc_descr(i_exc)%diff_r_abs**2 + &
    1025              :                                          exc_descr(i_exc)%sigma_e**2 + exc_descr(i_exc)%sigma_h**2 &
    1026          110 :                                          - 2*exc_descr(i_exc)%cov_e_h_sum)
    1027              : 
    1028          440 :       DO i_dir = 1, 3
    1029              :          exc_descr(i_exc)%d_exc_dir(i_dir) = SQRT(exc_descr(i_exc)%d_eh_dir(i_dir)**2 + &
    1030              :                                                   exc_descr(i_exc)%sigma_e_dir(i_dir)**2 + &
    1031              :                                                   exc_descr(i_exc)%sigma_h_dir(i_dir)**2 - &
    1032          440 :                                                   2*exc_descr(i_exc)%cov_e_h(i_dir, i_dir))
    1033              :       END DO
    1034              : 
    1035              :       ! Expectation values of r_e and r_h
    1036          440 :       exc_descr(i_exc)%r_e_shift(:) = exc_descr(i_exc)%r_e(:)
    1037          440 :       exc_descr(i_exc)%r_h_shift(:) = exc_descr(i_exc)%r_h(:)
    1038              : 
    1039          110 :       CALL cp_fm_struct_release(fm_struct_ia)
    1040          110 :       CALL cp_fm_struct_release(fm_struct_ab)
    1041              : 
    1042          110 :       CALL timestop(handle)
    1043              : 
    1044          330 :    END SUBROUTINE get_exciton_descriptors
    1045              : 
    1046            0 : END MODULE bse_properties
        

Generated by: LCOV version 2.0-1