LCOV - code coverage report
Current view: top level - src/fm - cp_cfm_elpa.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:24d69ee) Lines: 87.1 % 170 148
Test Date: 2026-09-03 07:32:15 Functions: 100.0 % 7 7

            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 Wrapper for ELPA (complex matrices, i.e. cp_cfm_type)
      10              : ! **************************************************************************************************
      11              : MODULE cp_cfm_elpa
      12              :    USE cp_blacs_env, ONLY: cp_blacs_env_create, &
      13              :                            cp_blacs_env_release, &
      14              :                            cp_blacs_env_type
      15              :    USE cp_cfm_basic_linalg, ONLY: cp_cfm_gemm, &
      16              :                                   cp_cfm_uplo_to_full
      17              :    USE cp_cfm_types, ONLY: cp_cfm_create, &
      18              :                            cp_cfm_release, &
      19              :                            cp_cfm_to_cfm, &
      20              :                            cp_cfm_type
      21              :    USE cp_fm_diag_utils, ONLY: cp_cfm_redistribute_start, &
      22              :                                cp_cfm_redistribute_end, &
      23              :                                cp_fm_redistribute_info
      24              :    USE cp_fm_elpa, ONLY: elpa_one_stage, &
      25              :                          elpa_print
      26              :    USE cp_fm_struct, ONLY: cp_fm_struct_create, &
      27              :                            cp_fm_struct_get, &
      28              :                            cp_fm_struct_release, &
      29              :                            cp_fm_struct_type
      30              :    USE cp_log_handling, ONLY: cp_get_default_logger, &
      31              :                               cp_logger_get_default_io_unit, &
      32              :                               cp_logger_type, &
      33              :                               cp_to_string
      34              :    USE kinds, ONLY: default_string_length, dp
      35              :    USE machine, ONLY: m_cpuid_static, &
      36              :                       MACHINE_CPU_GENERIC, &
      37              :                       MACHINE_X86_SSE4, &
      38              :                       MACHINE_X86_AVX, &
      39              :                       MACHINE_X86_AVX2, &
      40              :                       MACHINE_X86_AVX512
      41              :    USE message_passing, ONLY: mp_comm_self, &
      42              :                               mp_comm_type, &
      43              :                               mp_para_env_type
      44              :    USE OMP_LIB, ONLY: omp_get_max_threads
      45              :    USE parallel_rng_types, ONLY: rng_stream_type, &
      46              :                                  UNIFORM
      47              : #if defined(__HAS_IEEE_EXCEPTIONS)
      48              :    USE ieee_exceptions, ONLY: ieee_get_halting_mode, &
      49              :                               ieee_set_halting_mode, &
      50              :                               IEEE_ALL
      51              : #endif
      52              : #include "../base/base_uses.f90"
      53              : 
      54              : #if defined(__ELPA)
      55              :    USE elpa_constants, ONLY: ELPA_SOLVER_1STAGE, ELPA_SOLVER_2STAGE, ELPA_OK, &
      56              :                              ELPA_2STAGE_COMPLEX_INVALID, &
      57              :                              ELPA_2STAGE_COMPLEX_DEFAULT, &
      58              :                              ELPA_2STAGE_COMPLEX_GENERIC, &
      59              :                              ELPA_2STAGE_COMPLEX_GENERIC_SIMPLE, &
      60              :                              ELPA_2STAGE_COMPLEX_BGP, &
      61              :                              ELPA_2STAGE_COMPLEX_BGQ, &
      62              :                              ELPA_2STAGE_COMPLEX_SSE_BLOCK1, &
      63              :                              ELPA_2STAGE_COMPLEX_SSE_BLOCK2, &
      64              :                              ELPA_2STAGE_COMPLEX_AVX_BLOCK1, &
      65              :                              ELPA_2STAGE_COMPLEX_AVX_BLOCK2, &
      66              :                              ELPA_2STAGE_COMPLEX_AVX2_BLOCK1, &
      67              :                              ELPA_2STAGE_COMPLEX_AVX2_BLOCK2, &
      68              :                              ELPA_2STAGE_COMPLEX_AVX512_BLOCK1, &
      69              :                              ELPA_2STAGE_COMPLEX_AVX512_BLOCK2, &
      70              :                              ELPA_2STAGE_COMPLEX_NVIDIA_GPU, &
      71              :                              ELPA_2STAGE_COMPLEX_AMD_GPU, &
      72              :                              ELPA_2STAGE_COMPLEX_INTEL_GPU_SYCL
      73              : 
      74              :    USE elpa, ONLY: elpa_t, &
      75              :                    elpa_allocate, elpa_deallocate
      76              : #endif
      77              : 
      78              :    IMPLICIT NONE
      79              : 
      80              :    PRIVATE
      81              : 
      82              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'cp_cfm_elpa'
      83              : 
      84              : #if defined(__ELPA)
      85              :    INTEGER, DIMENSION(16), PARAMETER :: elpa_c_kernel_ids = [ &
      86              :                                         ELPA_2STAGE_COMPLEX_INVALID, & ! auto
      87              :                                         ELPA_2STAGE_COMPLEX_GENERIC, &
      88              :                                         ELPA_2STAGE_COMPLEX_GENERIC_SIMPLE, &
      89              :                                         ELPA_2STAGE_COMPLEX_BGP, &
      90              :                                         ELPA_2STAGE_COMPLEX_BGQ, &
      91              :                                         ELPA_2STAGE_COMPLEX_SSE_BLOCK1, &
      92              :                                         ELPA_2STAGE_COMPLEX_SSE_BLOCK2, &
      93              :                                         ELPA_2STAGE_COMPLEX_AVX_BLOCK1, &
      94              :                                         ELPA_2STAGE_COMPLEX_AVX_BLOCK2, &
      95              :                                         ELPA_2STAGE_COMPLEX_AVX2_BLOCK1, &
      96              :                                         ELPA_2STAGE_COMPLEX_AVX2_BLOCK2, &
      97              :                                         ELPA_2STAGE_COMPLEX_AVX512_BLOCK1, &
      98              :                                         ELPA_2STAGE_COMPLEX_AVX512_BLOCK2, &
      99              :                                         ELPA_2STAGE_COMPLEX_NVIDIA_GPU, &
     100              :                                         ELPA_2STAGE_COMPLEX_AMD_GPU, &
     101              :                                         ELPA_2STAGE_COMPLEX_INTEL_GPU_SYCL]
     102              : 
     103              :    CHARACTER(len=14), DIMENSION(SIZE(elpa_c_kernel_ids)), PARAMETER :: &
     104              :       elpa_c_kernel_names = [CHARACTER(len=14) :: &
     105              :                              "AUTO", &
     106              :                              "GENERIC", &
     107              :                              "GENERIC_SIMPLE", &
     108              :                              "BGP", &
     109              :                              "BGQ", &
     110              :                              "SSE_BLOCK1", &
     111              :                              "SSE_BLOCK2", &
     112              :                              "AVX_BLOCK1", &
     113              :                              "AVX_BLOCK2", &
     114              :                              "AVX2_BLOCK1", &
     115              :                              "AVX2_BLOCK2", &
     116              :                              "AVX512_BLOCK1", &
     117              :                              "AVX512_BLOCK2", &
     118              :                              "NVIDIA_GPU", &
     119              :                              "AMD_GPU", &
     120              :                              "INTEL_GPU"]
     121              : 
     122              :    CHARACTER(len=44), DIMENSION(SIZE(elpa_c_kernel_ids)), PARAMETER :: &
     123              :       elpa_c_kernel_descriptions = [CHARACTER(len=44) :: &
     124              :                                     "Automatically selected kernel", &
     125              :                                     "Generic kernel", &
     126              :                                     "Simplified generic kernel", &
     127              :                                     "Kernel optimized for IBM BGP", &
     128              :                                     "Kernel optimized for IBM BGQ", &
     129              :                                     "Kernel optimized for x86_64/SSE (block=1)", &
     130              :                                     "Kernel optimized for x86_64/SSE (block=2)", &
     131              :                                     "Kernel optimized for Intel AVX (block=1)", &
     132              :                                     "Kernel optimized for Intel AVX (block=2)", &
     133              :                                     "Kernel optimized for Intel AVX2 (block=1)", &
     134              :                                     "Kernel optimized for Intel AVX2 (block=2)", &
     135              :                                     "Kernel optimized for Intel AVX-512 (block=1)", &
     136              :                                     "Kernel optimized for Intel AVX-512 (block=2)", &
     137              :                                     "Kernel targeting Nvidia GPUs", &
     138              :                                     "Kernel targeting AMD GPUs", &
     139              :                                     "Kernel targeting Intel GPUs"]
     140              : #else
     141              :    INTEGER, DIMENSION(1), PARAMETER :: elpa_c_kernel_ids = [-1]
     142              :    CHARACTER(len=14), DIMENSION(1), PARAMETER :: elpa_c_kernel_names = ["AUTO"]
     143              :    CHARACTER(len=44), DIMENSION(1), PARAMETER :: elpa_c_kernel_descriptions = ["Automatically selected kernel"]
     144              : #endif
     145              : 
     146              : #if defined(__ELPA)
     147              :    INTEGER, SAVE :: elpa_c_kernel = elpa_c_kernel_ids(1) ! auto
     148              : #endif
     149              : 
     150              :    ! Runtime correctness check state
     151              :    LOGICAL, SAVE, PRIVATE :: elpa_c_correctness_checked = .FALSE.
     152              :    LOGICAL, SAVE, PRIVATE :: elpa_c_broken = .FALSE.
     153              : 
     154              :    PUBLIC :: cp_cfm_diag_elpa, &
     155              :              set_elpa_c_kernel, &
     156              :              check_elpa_c_kernel_correctness, &
     157              :              is_elpa_c_broken, &
     158              :              elpa_c_kernel_ids, &
     159              :              elpa_c_kernel_names, &
     160              :              elpa_c_kernel_descriptions
     161              : 
     162              : CONTAINS
     163              : 
     164              : #if defined(__ELPA)
     165              : ! **************************************************************************************************
     166              : !> \brief Return a printable name for an ELPA complex kernel.
     167              : !> \param kernel ELPA complex kernel id
     168              : !> \return ...
     169              : ! **************************************************************************************************
     170           29 :    FUNCTION get_elpa_c_kernel_name(kernel) RESULT(kernel_name)
     171              :       INTEGER, INTENT(IN)                                :: kernel
     172              :       CHARACTER(len=default_string_length)               :: kernel_name
     173              : 
     174              :       INTEGER                                            :: i
     175              : 
     176           29 :       kernel_name = "id: "//TRIM(ADJUSTL(cp_to_string(kernel)))
     177          211 :       DO i = 1, SIZE(elpa_c_kernel_ids)
     178          211 :          IF (elpa_c_kernel_ids(i) == kernel) THEN
     179           29 :             kernel_name = elpa_c_kernel_names(i)
     180           29 :             EXIT
     181              :          END IF
     182              :       END DO
     183           29 :    END FUNCTION get_elpa_c_kernel_name
     184              : #endif
     185              : 
     186              : ! **************************************************************************************************
     187              : !> \brief Sets the active ELPA kernel for complex matrices.
     188              : !> \param requested_kernel one of the elpa_c_kernel_ids
     189              : ! **************************************************************************************************
     190        10646 :    SUBROUTINE set_elpa_c_kernel(requested_kernel)
     191              :       INTEGER, INTENT(IN)                                :: requested_kernel
     192              : 
     193              : #if defined(__ELPA)
     194              :       INTEGER                                            :: cpuid
     195              : 
     196        10646 :       elpa_c_kernel = requested_kernel
     197              : 
     198              :       ! Resolve AUTO kernel.
     199        10646 :       IF (elpa_c_kernel == ELPA_2STAGE_COMPLEX_INVALID) THEN
     200        10640 :          cpuid = m_cpuid_static()
     201            0 :          SELECT CASE (cpuid)
     202              :          CASE (MACHINE_CPU_GENERIC)
     203            0 :             elpa_c_kernel = ELPA_2STAGE_COMPLEX_GENERIC
     204              :          CASE (MACHINE_X86_SSE4)
     205            0 :             elpa_c_kernel = ELPA_2STAGE_COMPLEX_SSE_BLOCK2
     206              :          CASE (MACHINE_X86_AVX)
     207            0 :             elpa_c_kernel = ELPA_2STAGE_COMPLEX_AVX_BLOCK2
     208              :          CASE (MACHINE_X86_AVX2)
     209        10640 :             elpa_c_kernel = ELPA_2STAGE_COMPLEX_AVX2_BLOCK2
     210              :          CASE (MACHINE_X86_AVX512)
     211        10640 :             elpa_c_kernel = ELPA_2STAGE_COMPLEX_AVX512_BLOCK2
     212              :          END SELECT
     213              : 
     214              :          ! Prefer GPU kernel if available.
     215              : #if !defined(__NO_OFFLOAD_ELPA)
     216              : #if defined(__OFFLOAD_CUDA)
     217              :          elpa_c_kernel = ELPA_2STAGE_COMPLEX_NVIDIA_GPU
     218              : #endif
     219              : #if defined(__OFFLOAD_HIP)
     220              :          elpa_c_kernel = ELPA_2STAGE_COMPLEX_AMD_GPU
     221              : #endif
     222              : #if defined(__OFFLOAD_OPENCL)
     223              :          elpa_c_kernel = ELPA_2STAGE_COMPLEX_INTEL_GPU_SYCL
     224              : #endif
     225              : #endif
     226              :          ! If we could not find a suitable kernel then use ELPA_2STAGE_COMPLEX_DEFAULT.
     227        10640 :          IF (elpa_c_kernel == ELPA_2STAGE_COMPLEX_INVALID) THEN
     228            0 :             elpa_c_kernel = ELPA_2STAGE_COMPLEX_DEFAULT
     229              :          END IF
     230              :       END IF
     231              : #else
     232              :       MARK_USED(requested_kernel)
     233              : #endif
     234        10646 :    END SUBROUTINE set_elpa_c_kernel
     235              : 
     236              : #if defined(__ELPA)
     237              : ! **************************************************************************************************
     238              : !> \brief Returns .TRUE. if the complex kernel is an SSE/AVX/AVX2/AVX512 BLOCK2 variant
     239              : !>        (these may be affected by the GCC 15.2 regression, marekandreas/elpa#77).
     240              : !> \param kernel ...
     241              : !> \return ...
     242              : ! **************************************************************************************************
     243           32 :    FUNCTION is_block2_c_kernel(kernel) RESULT(is_block2)
     244              :       INTEGER, INTENT(IN)                                :: kernel
     245              :       LOGICAL                                            :: is_block2
     246              : 
     247              :       is_block2 = (kernel == ELPA_2STAGE_COMPLEX_SSE_BLOCK2) .OR. &
     248              :                   (kernel == ELPA_2STAGE_COMPLEX_AVX_BLOCK2) .OR. &
     249              :                   (kernel == ELPA_2STAGE_COMPLEX_AVX2_BLOCK2) .OR. &
     250           32 :                   (kernel == ELPA_2STAGE_COMPLEX_AVX512_BLOCK2)
     251           32 :    END FUNCTION is_block2_c_kernel
     252              : #endif
     253              : 
     254              : ! **************************************************************************************************
     255              : !> \brief One-time runtime correctness check for ELPA complex BLOCK2 kernels.
     256              : !>        A small deterministic eigenproblem is solved on a single process with the
     257              : !>        same production routine used for the actual diagonalizations, and the
     258              : !>        eigenpair residual as well as the eigenvector orthogonality are verified.
     259              : !>        This guards against mis-compiled kernels (GCC 15.2 regression, marekandreas/elpa#77).
     260              : !>        If the check fails, a module flag is set such that cp_cfm_heevd falls back to ScaLAPACK.
     261              : !> \param para_env communicator of the run, used to agree on the check result
     262              : ! **************************************************************************************************
     263           32 :    SUBROUTINE check_elpa_c_kernel_correctness(para_env)
     264              :       TYPE(mp_para_env_type), INTENT(IN)                 :: para_env
     265              : 
     266              : #if defined(__ELPA)
     267              :       CHARACTER(LEN=*), PARAMETER                        :: routineN = 'check_elpa_c_kernel_correctness'
     268              :       CHARACTER(LEN=4*default_string_length)             :: message
     269              :       INTEGER                                            :: handle, i, is_broken, j
     270              :       ! Geometry aligned with the standalone reproducer of marekandreas/elpa#77.
     271              :       INTEGER, PARAMETER                                 :: na_test = 20, nev_test = 18, nblk_test = 8
     272              :       REAL(KIND=dp)                                      :: eps_ortho, eps_residual, rnd_im, rnd_re, test
     273              :       REAL(KIND=dp), PARAMETER                           :: th = 1.0E-8_dp
     274           32 :       REAL(KIND=dp), DIMENSION(:), ALLOCATABLE           :: eigenvalues
     275              :       TYPE(cp_blacs_env_type), POINTER                   :: context
     276              :       TYPE(cp_cfm_type)                                  :: eigenvectors, matrix, matrix_ref
     277              :       TYPE(cp_fm_redistribute_info)                      :: rdinfo
     278              :       TYPE(cp_fm_struct_type), POINTER                   :: fmstruct
     279              :       TYPE(mp_para_env_type)                             :: para_env_self
     280              :       TYPE(rng_stream_type)                              :: rng_stream
     281              : 
     282           32 :       IF (elpa_c_correctness_checked) RETURN ! one-shot per process
     283              : 
     284           32 :       IF (.NOT. is_block2_c_kernel(elpa_c_kernel)) RETURN ! only BLOCK2 kernels are affected
     285           30 :       IF (elpa_one_stage) RETURN ! the 1-stage solver does not use kernels
     286           24 :       elpa_c_correctness_checked = .TRUE.
     287              : 
     288           24 :       CALL timeset(routineN, handle)
     289              : 
     290              :       ! Solve a small deterministic Hermitian eigenproblem redundantly on every process,
     291              :       ! reusing the production ELPA path (kernel setup, fallbacks and solver call).
     292           24 :       para_env_self = mp_comm_self
     293           24 :       CALL cp_blacs_env_create(context, para_env_self)
     294              :       CALL cp_fm_struct_create(fmstruct, para_env=para_env_self, context=context, &
     295              :                                nrow_global=na_test, ncol_global=na_test, &
     296           24 :                                nrow_block=nblk_test, ncol_block=nblk_test)
     297           24 :       CALL cp_cfm_create(matrix, fmstruct, name="elpa_check_mat")
     298           24 :       CALL cp_cfm_create(matrix_ref, fmstruct, name="elpa_check_ref")
     299           24 :       CALL cp_cfm_create(eigenvectors, fmstruct, name="elpa_check_vec")
     300              : 
     301              :       ! Fill with a dense random Hermitian matrix; the RNG stream starts from the
     302              :       ! library default seed, hence the matrix is identical on all processes.
     303           24 :       rng_stream = rng_stream_type(name="elpa_kernel_correctness", distribution_type=UNIFORM)
     304          504 :       DO j = 1, na_test
     305         5040 :          DO i = 1, j - 1
     306         4560 :             rnd_re = 2.0_dp*rng_stream%next() - 1.0_dp
     307         4560 :             rnd_im = 2.0_dp*rng_stream%next() - 1.0_dp
     308         4560 :             matrix%local_data(i, j) = CMPLX(rnd_re, rnd_im, KIND=dp)
     309         5040 :             matrix%local_data(j, i) = CONJG(matrix%local_data(i, j))
     310              :          END DO
     311          480 :          rnd_re = 2.0_dp*rng_stream%next() - 1.0_dp
     312          504 :          matrix%local_data(j, j) = CMPLX(rnd_re, 0.0_dp, KIND=dp)
     313              :       END DO
     314           24 :       CALL cp_cfm_to_cfm(matrix, matrix_ref) ! the solver destroys its input matrix
     315              : 
     316           24 :       ALLOCATE (eigenvalues(nev_test))
     317           24 :       CALL cp_cfm_diag_elpa_base(matrix, eigenvectors, eigenvalues, rdinfo)
     318              : 
     319              :       ! Check the orthogonality of the eigenvectors, |Z^H*Z - I|
     320              :       CALL cp_cfm_gemm("C", "N", nev_test, nev_test, na_test, (1.0_dp, 0.0_dp), &
     321           24 :                        eigenvectors, eigenvectors, (0.0_dp, 0.0_dp), matrix)
     322           24 :       eps_ortho = 0.0_dp
     323          456 :       outer_ortho: DO i = 1, nev_test
     324         8232 :          DO j = 1, nev_test
     325         7776 :             test = ABS(matrix%local_data(i, j))
     326         7776 :             IF (i == j) test = ABS(matrix%local_data(i, j) - (1.0_dp, 0.0_dp))
     327         7776 :             IF (test > eps_ortho) eps_ortho = test
     328         8208 :             IF (eps_ortho > th) EXIT outer_ortho
     329              :          END DO
     330              :       END DO outer_ortho
     331              : 
     332              :       ! Check the eigenpair residuals, |A*Z - Z*diag(eigenvalues)|
     333              :       CALL cp_cfm_gemm("N", "N", na_test, nev_test, na_test, (1.0_dp, 0.0_dp), &
     334           24 :                        matrix_ref, eigenvectors, (0.0_dp, 0.0_dp), matrix)
     335           24 :       eps_residual = 0.0_dp
     336          504 :       outer_res: DO i = 1, na_test
     337         9144 :          DO j = 1, nev_test
     338         8640 :             test = ABS(matrix%local_data(i, j) - eigenvectors%local_data(i, j)*eigenvalues(j))
     339         8640 :             IF (test > eps_residual) eps_residual = test
     340         9120 :             IF (eps_residual > th) EXIT outer_res
     341              :          END DO
     342              :       END DO outer_res
     343              : 
     344              :       ! Mis-compiled kernels produce grossly wrong results (deviations ~1e-1), not subtle rounding.
     345           24 :       elpa_c_broken = (eps_ortho > th) .OR. (eps_residual > th)
     346              : 
     347              :       ! Agree on the result to guarantee a consistent fallback on all processes.
     348           24 :       is_broken = 0
     349           24 :       IF (elpa_c_broken) is_broken = 1
     350           24 :       CALL para_env%max(is_broken)
     351           24 :       elpa_c_broken = is_broken == 1
     352           24 :       IF (elpa_c_broken) THEN
     353              :          message = "The ELPA complex kernel "//TRIM(get_elpa_c_kernel_name(elpa_c_kernel))// &
     354              :                    " failed a runtime correctness check (orthogonality: "//TRIM(cp_to_string(eps_ortho))// &
     355              :                    ", residual: "//TRIM(cp_to_string(eps_residual))//") and may return incorrect"// &
     356              :                    " eigenvectors. Complex matrices fall back to ScaLAPACK. Consider the GENERIC"// &
     357            0 :                    " kernel or rebuilding ELPA with -fno-tree-slp-vectorize."
     358            0 :          CALL cp_warn(__LOCATION__, TRIM(message))
     359              :       END IF
     360              : 
     361           24 :       DEALLOCATE (eigenvalues)
     362           24 :       CALL cp_cfm_release(eigenvectors)
     363           24 :       CALL cp_cfm_release(matrix_ref)
     364           24 :       CALL cp_cfm_release(matrix)
     365           24 :       CALL cp_fm_struct_release(fmstruct)
     366           24 :       CALL cp_blacs_env_release(context)
     367              : 
     368           24 :       CALL timestop(handle)
     369              : #else
     370              :       MARK_USED(para_env)
     371              : #endif
     372          848 :    END SUBROUTINE check_elpa_c_kernel_correctness
     373              : 
     374              : ! **************************************************************************************************
     375              : !> \brief Returns .TRUE. if the runtime check detected broken BLOCK2 kernels.
     376              : !> \return ...
     377              : ! **************************************************************************************************
     378           72 :    FUNCTION is_elpa_c_broken() RESULT(broken)
     379              :       LOGICAL                                            :: broken
     380              : 
     381           72 :       broken = elpa_c_broken
     382           72 :    END FUNCTION is_elpa_c_broken
     383              : 
     384              : ! **************************************************************************************************
     385              : !> \brief Driver routine to diagonalize a CFM matrix with the ELPA library.
     386              : !> \param matrix the matrix that is diagonalized
     387              : !> \param eigenvectors eigenvectors of the input matrix
     388              : !> \param eigenvalues eigenvalues of the input matrix
     389              : ! **************************************************************************************************
     390           72 :    SUBROUTINE cp_cfm_diag_elpa(matrix, eigenvectors, eigenvalues)
     391              :       TYPE(cp_cfm_type), INTENT(IN)          :: matrix, eigenvectors
     392              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: eigenvalues
     393              : 
     394              : #if defined(__ELPA)
     395              :       CHARACTER(len=*), PARAMETER :: routineN = 'cp_cfm_diag_elpa'
     396              : 
     397              :       INTEGER                                  :: handle
     398              :       TYPE(cp_cfm_type)                         :: eigenvectors_new, matrix_new
     399              :       TYPE(cp_fm_redistribute_info)            :: rdinfo
     400              : 
     401           72 :       CALL timeset(routineN, handle)
     402              : 
     403              :       ! Determine if the input matrix needs to be redistributed before diagonalization.
     404              :       ! Heuristics are used to determine the optimal number of CPUs for diagonalization.
     405              :       ! The redistributed matrix is stored in matrix_new, which is just a pointer
     406              :       ! to the original matrix if no redistribution is required.
     407              :       ! With ELPA, we have to make sure that all processor columns have nonzero width
     408              :       CALL cp_cfm_redistribute_start(matrix, eigenvectors, matrix_new, eigenvectors_new, &
     409           72 :                                      caller_is_elpa=.TRUE., redist_info=rdinfo)
     410              : 
     411              :       ! Call ELPA on CPUs that hold the new matrix
     412           72 :       IF (ASSOCIATED(matrix_new%matrix_struct)) THEN
     413           36 :          CALL cp_cfm_diag_elpa_base(matrix_new, eigenvectors_new, eigenvalues, rdinfo)
     414              :       END IF
     415              : 
     416              :       ! Redistribute results and clean up
     417           72 :       CALL cp_cfm_redistribute_end(matrix, eigenvectors, eigenvalues, matrix_new, eigenvectors_new)
     418              : 
     419           72 :       CALL timestop(handle)
     420              : #else
     421              :       eigenvalues = 0
     422              :       MARK_USED(matrix)
     423              :       MARK_USED(eigenvectors)
     424              : 
     425              :       CPABORT("CP2K compiled without the ELPA library.")
     426              : #endif
     427           72 :    END SUBROUTINE cp_cfm_diag_elpa
     428              : 
     429              : #if defined(__ELPA)
     430              : ! **************************************************************************************************
     431              : !> \brief Actual routine that calls ELPA to diagonalize a CFM matrix.
     432              : !> \param matrix the matrix that is diagonalized
     433              : !> \param eigenvectors eigenvectors of the input matrix
     434              : !> \param eigenvalues eigenvalues of the input matrix
     435              : !> \param rdinfo ...
     436              : ! **************************************************************************************************
     437           60 :    SUBROUTINE cp_cfm_diag_elpa_base(matrix, eigenvectors, eigenvalues, rdinfo)
     438              : 
     439              :       TYPE(cp_cfm_type), INTENT(IN)                      :: matrix, eigenvectors
     440              :       REAL(KIND=dp), DIMENSION(:), INTENT(OUT)           :: eigenvalues
     441              :       TYPE(cp_fm_redistribute_info), INTENT(IN)          :: rdinfo
     442              : 
     443              :       CHARACTER(len=*), PARAMETER :: routineN = 'cp_cfm_diag_elpa_base'
     444              : 
     445              :       INTEGER                                            :: handle
     446              : 
     447              :       CLASS(elpa_t), POINTER                   :: elpa_obj
     448              :       CHARACTER(len=default_string_length)     :: kernel_name
     449              :       CHARACTER(len=2*default_string_length)   :: message
     450              :       TYPE(mp_comm_type) :: group
     451              :       INTEGER                                  :: fallback_kernel, &
     452              :                                                   mypcol, myprow, n, &
     453              :                                                   n_rows, n_cols, &
     454              :                                                   nblk, neig, io_unit, &
     455              :                                                   success
     456           60 :       REAL(KIND=dp), DIMENSION(:), ALLOCATABLE :: eval
     457              :       TYPE(cp_blacs_env_type), POINTER         :: context
     458              :       TYPE(cp_logger_type), POINTER            :: logger
     459           60 :       INTEGER, DIMENSION(:), POINTER           :: ncol_locals
     460              : #if defined(__HAS_IEEE_EXCEPTIONS)
     461              :       LOGICAL, DIMENSION(5)                    :: halt
     462              : #endif
     463              : 
     464           60 :       CALL timeset(routineN, handle)
     465           60 :       NULLIFY (logger)
     466           60 :       NULLIFY (ncol_locals)
     467              : 
     468           60 :       logger => cp_get_default_logger()
     469           60 :       io_unit = cp_logger_get_default_io_unit(logger)
     470              : 
     471           60 :       n = matrix%matrix_struct%nrow_global
     472           60 :       context => matrix%matrix_struct%context
     473           60 :       group = matrix%matrix_struct%para_env
     474              : 
     475           60 :       myprow = context%mepos(1)
     476           60 :       mypcol = context%mepos(2)
     477              : 
     478              :       ! elpa needs the full matrix
     479           60 :       CALL cp_cfm_uplo_to_full(matrix, eigenvectors)
     480              : 
     481              :       CALL cp_fm_struct_get(matrix%matrix_struct, &
     482              :                             local_leading_dimension=n_rows, &
     483              :                             ncol_local=n_cols, &
     484              :                             nrow_block=nblk, &
     485           60 :                             ncol_locals=ncol_locals)
     486              : 
     487              :       ! ELPA will fail in 'solve_tridi', with no useful error message, fail earlier
     488          120 :       IF (io_unit > 0 .AND. ANY(ncol_locals == 0)) THEN
     489            0 :          CALL rdinfo%write(io_unit)
     490            0 :          CPABORT("ELPA [pre-fail]: Problem contains processor column with zero width.")
     491              :       END IF
     492              : 
     493           60 :       neig = SIZE(eigenvalues, 1)
     494              :       ! ELPA's QR decomposition is only available for real matrices
     495              : 
     496           60 :       IF (io_unit > 0 .AND. elpa_print) THEN
     497              :          WRITE (UNIT=io_unit, FMT="(/,T2,A)") &
     498           29 :             "ELPA| Matrix diagonalization information"
     499              : 
     500           29 :          kernel_name = get_elpa_c_kernel_name(elpa_c_kernel)
     501              : 
     502              :          WRITE (UNIT=io_unit, FMT="(T2,A,T71,I10)") &
     503           29 :             "ELPA| Matrix order (NA) ", n, &
     504           29 :             "ELPA| Matrix block size (NBLK) ", nblk, &
     505           29 :             "ELPA| Number of eigenvectors (NEV) ", neig, &
     506           29 :             "ELPA| Local rows (LOCAL_NROWS) ", n_rows, &
     507           58 :             "ELPA| Local columns (LOCAL_NCOLS) ", n_cols
     508              :          WRITE (UNIT=io_unit, FMT="(T2,A,T61,A20)") &
     509           29 :             "ELPA| Kernel ", ADJUSTR(TRIM(kernel_name))
     510              :       END IF
     511              : 
     512              :       ! the full eigenvalues vector is needed
     513          180 :       ALLOCATE (eval(n))
     514              : 
     515           60 :       elpa_obj => elpa_allocate()
     516              : 
     517           60 :       CALL elpa_obj%set("na", n, success)
     518           60 :       CPASSERT(success == ELPA_OK)
     519              : 
     520           60 :       CALL elpa_obj%set("nev", neig, success)
     521           60 :       CPASSERT(success == ELPA_OK)
     522              : 
     523           60 :       CALL elpa_obj%set("local_nrows", n_rows, success)
     524           60 :       CPASSERT(success == ELPA_OK)
     525              : 
     526           60 :       CALL elpa_obj%set("local_ncols", n_cols, success)
     527           60 :       CPASSERT(success == ELPA_OK)
     528              : 
     529           60 :       CALL elpa_obj%set("nblk", nblk, success)
     530           60 :       CPASSERT(success == ELPA_OK)
     531              : 
     532           60 :       CALL elpa_obj%set("mpi_comm_parent", group%get_handle(), success)
     533           60 :       CPASSERT(success == ELPA_OK)
     534              : 
     535           60 :       CALL elpa_obj%set("process_row", myprow, success)
     536           60 :       CPASSERT(success == ELPA_OK)
     537              : 
     538           60 :       CALL elpa_obj%set("process_col", mypcol, success)
     539           60 :       CPASSERT(success == ELPA_OK)
     540              : 
     541           60 :       success = elpa_obj%setup()
     542           60 :       CPASSERT(success == ELPA_OK)
     543              : 
     544              :       CALL elpa_obj%set("solver", &
     545              :                         MERGE(ELPA_SOLVER_1STAGE, ELPA_SOLVER_2STAGE, elpa_one_stage), &
     546          108 :                         success)
     547           60 :       IF (success /= ELPA_OK) THEN
     548            0 :          CPABORT("Setting solver for ELPA failed")
     549              :       END IF
     550              : 
     551              :       ! enabling the GPU must happen before setting the kernel
     552            0 :       SELECT CASE (elpa_c_kernel)
     553              :       CASE (ELPA_2STAGE_COMPLEX_NVIDIA_GPU)
     554            0 :          CALL elpa_obj%set("nvidia-gpu", 1, success)
     555            0 :          CPASSERT(success == ELPA_OK)
     556              :       CASE (ELPA_2STAGE_COMPLEX_AMD_GPU)
     557            0 :          CALL elpa_obj%set("amd-gpu", 1, success)
     558            0 :          CPASSERT(success == ELPA_OK)
     559              :       CASE (ELPA_2STAGE_COMPLEX_INTEL_GPU_SYCL)
     560            0 :          CALL elpa_obj%set("intel-gpu", 1, success)
     561           60 :          CPASSERT(success == ELPA_OK)
     562              :       END SELECT
     563              : 
     564           60 :       IF (.NOT. elpa_one_stage) THEN
     565              :          ! Keep ELPA's configured default in case the requested kernel is unavailable.
     566           48 :          CALL elpa_obj%get("complex_kernel", fallback_kernel, success)
     567           48 :          CPASSERT(success == ELPA_OK)
     568              : 
     569           48 :          CALL elpa_obj%set("complex_kernel", elpa_c_kernel, success)
     570           48 :          IF (success /= ELPA_OK) THEN
     571              :             message = "Requested ELPA complex kernel "//TRIM(get_elpa_c_kernel_name(elpa_c_kernel))// &
     572            0 :                       " is unavailable; falling back to "//TRIM(get_elpa_c_kernel_name(fallback_kernel))
     573            0 :             CALL cp_warn(__LOCATION__, TRIM(message))
     574            0 :             CALL elpa_obj%set("complex_kernel", fallback_kernel, success)
     575            0 :             CPASSERT(success == ELPA_OK)
     576              :             ! Avoid retrying the unavailable kernel for every subsequent diagonalization.
     577            0 :             elpa_c_kernel = fallback_kernel
     578              :          END IF
     579              :       END IF
     580              : 
     581              :       ! Set number of threads only when ELPA was built with OpenMP support.
     582           60 :       IF (elpa_obj%can_set("omp_threads", omp_get_max_threads()) == ELPA_OK) THEN
     583           60 :          CALL elpa_obj%set("omp_threads", omp_get_max_threads(), success)
     584           60 :          CPASSERT(success == ELPA_OK)
     585              :       END IF
     586              : 
     587              :       ! ELPA solver: calculate the Eigenvalues/vectors
     588              : #if defined(__HAS_IEEE_EXCEPTIONS)
     589              :       CALL ieee_get_halting_mode(IEEE_ALL, halt)
     590              :       CALL ieee_set_halting_mode(IEEE_ALL, .FALSE.)
     591              : #endif
     592           60 :       CALL elpa_obj%eigenvectors(matrix%local_data, eval, eigenvectors%local_data, success)
     593              : #if defined(__HAS_IEEE_EXCEPTIONS)
     594              :       CALL ieee_set_halting_mode(IEEE_ALL, halt)
     595              : #endif
     596              : 
     597           60 :       IF (success /= ELPA_OK) THEN
     598            0 :          CPABORT("ELPA failed to diagonalize a matrix")
     599              :       END IF
     600              : 
     601           60 :       CALL elpa_deallocate(elpa_obj, success)
     602           60 :       CPASSERT(success == ELPA_OK)
     603              : 
     604         1896 :       eigenvalues(1:neig) = eval(1:neig)
     605           60 :       DEALLOCATE (eval)
     606              : 
     607           60 :       CALL timestop(handle)
     608              : 
     609          180 :    END SUBROUTINE cp_cfm_diag_elpa_base
     610              : #endif
     611              : 
     612              : END MODULE cp_cfm_elpa
        

Generated by: LCOV version 2.0-1