LCOV - code coverage report
Current view: top level - src - preconditioner_solvers.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:24d69ee) Lines: 77.9 % 122 95
Test Date: 2026-09-03 07:32:15 Functions: 85.7 % 7 6

            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 solves the preconditioner, contains to utility function for
      10              : !>        fm<->dbcsr transfers, should be moved soon
      11              : !> \par History
      12              : !>      - [UB] 2009-05-13 Adding stable approximate inverse (full and sparse)
      13              : !> \author Joost VandeVondele (09.2002)
      14              : ! **************************************************************************************************
      15              : MODULE preconditioner_solvers
      16              :    USE arnoldi_api,                     ONLY: arnoldi_env_type,&
      17              :                                               arnoldi_ev,&
      18              :                                               deallocate_arnoldi_env,&
      19              :                                               get_selected_ritz_val,&
      20              :                                               setup_arnoldi_env
      21              :    USE bibliography,                    ONLY: Schiffmann2015,&
      22              :                                               cite_reference
      23              :    USE cp_blacs_env,                    ONLY: cp_blacs_env_type
      24              :    USE cp_dbcsr_api,                    ONLY: &
      25              :         dbcsr_create, dbcsr_filter, dbcsr_get_info, dbcsr_get_occupation, dbcsr_init_p, &
      26              :         dbcsr_p_type, dbcsr_release, dbcsr_type, dbcsr_type_no_symmetry
      27              :    USE cp_dbcsr_contrib,                ONLY: dbcsr_gershgorin_norm
      28              :    USE cp_dbcsr_operations,             ONLY: copy_dbcsr_to_fm,&
      29              :                                               copy_fm_to_dbcsr
      30              :    USE cp_fm_basic_linalg,              ONLY: cp_fm_uplo_to_full
      31              :    USE cp_fm_cholesky,                  ONLY: cp_fm_cholesky_decompose,&
      32              :                                               cp_fm_cholesky_invert
      33              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      34              :                                               cp_fm_struct_release,&
      35              :                                               cp_fm_struct_type
      36              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      37              :                                               cp_fm_release,&
      38              :                                               cp_fm_set_all,&
      39              :                                               cp_fm_type
      40              :    USE input_constants,                 ONLY: ot_precond_full_kinetic,&
      41              :                                               ot_precond_solver_chebyshev,&
      42              :                                               ot_precond_solver_default,&
      43              :                                               ot_precond_solver_direct,&
      44              :                                               ot_precond_solver_inv_chol,&
      45              :                                               ot_precond_solver_update
      46              :    USE iterate_matrix,                  ONLY: invert_Hotelling
      47              :    USE kinds,                           ONLY: dp
      48              :    USE message_passing,                 ONLY: mp_para_env_type
      49              :    USE preconditioner_types,            ONLY: preconditioner_type
      50              : #include "./base/base_uses.f90"
      51              : 
      52              :    IMPLICIT NONE
      53              : 
      54              :    PRIVATE
      55              : 
      56              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'preconditioner_solvers'
      57              : 
      58              :    PUBLIC :: solve_preconditioner, transfer_fm_to_dbcsr, transfer_dbcsr_to_fm
      59              : 
      60              : CONTAINS
      61              : 
      62              : ! **************************************************************************************************
      63              : !> \brief ...
      64              : !> \param my_solver_type ...
      65              : !> \param preconditioner_env ...
      66              : !> \param matrix_s ...
      67              : !> \param matrix_h ...
      68              : ! **************************************************************************************************
      69         9592 :    SUBROUTINE solve_preconditioner(my_solver_type, preconditioner_env, matrix_s, &
      70              :                                    matrix_h)
      71              :       INTEGER                                            :: my_solver_type
      72              :       TYPE(preconditioner_type)                          :: preconditioner_env
      73              :       TYPE(dbcsr_type), OPTIONAL, POINTER                :: matrix_s
      74              :       TYPE(dbcsr_type), POINTER                          :: matrix_h
      75              : 
      76              :       REAL(dp)                                           :: occ_matrix
      77              : 
      78              : ! here comes the solver
      79              : 
      80        15424 :       SELECT CASE (my_solver_type)
      81              :       CASE (ot_precond_solver_inv_chol)
      82              :          !
      83              :          ! compute the full inverse
      84         5832 :          preconditioner_env%solver = ot_precond_solver_inv_chol
      85         5832 :          CALL make_full_inverse_cholesky(preconditioner_env, matrix_s)
      86              :       CASE (ot_precond_solver_direct)
      87              :          !
      88              :          ! prepare for the direct solver
      89            0 :          preconditioner_env%solver = ot_precond_solver_direct
      90            0 :          CALL make_full_fact_cholesky(preconditioner_env, matrix_s)
      91              :       CASE (ot_precond_solver_update)
      92              :          !
      93              :          ! uses an update of the full inverse (needs to be computed the first time)
      94              :          ! make sure preconditioner_env is not destroyed in between
      95            6 :          occ_matrix = 1.0_dp
      96            6 :          IF (ASSOCIATED(preconditioner_env%sparse_matrix)) THEN
      97            6 :             IF (preconditioner_env%condition_num < 0.0_dp) THEN
      98            2 :                CALL estimate_cond_num(preconditioner_env%sparse_matrix, preconditioner_env%condition_num)
      99              :             END IF
     100              :             CALL dbcsr_filter(preconditioner_env%sparse_matrix, &
     101            6 :                               1.0_dp/preconditioner_env%condition_num*0.01_dp)
     102            6 :             occ_matrix = dbcsr_get_occupation(preconditioner_env%sparse_matrix)
     103              :          END IF
     104              :          ! check whether we are in the first step and if it is a good idea to use cholesky (matrix sparsity)
     105            6 :          IF (preconditioner_env%solver /= ot_precond_solver_update .AND. occ_matrix > 0.5_dp) THEN
     106            2 :             preconditioner_env%solver = ot_precond_solver_update
     107            2 :             CALL make_full_inverse_cholesky(preconditioner_env, matrix_s)
     108              :          ELSE
     109            4 :             preconditioner_env%solver = ot_precond_solver_update
     110            4 :             CALL make_inverse_update(preconditioner_env, matrix_h)
     111              :          END IF
     112              :       CASE (ot_precond_solver_chebyshev)
     113            2 :          IF (preconditioner_env%in_use /= ot_precond_full_kinetic) THEN
     114            0 :             CPABORT("PRECOND_SOLVER CHEBYSHEV currently requires PRECONDITIONER FULL_KINETIC")
     115              :          END IF
     116            2 :          preconditioner_env%solver = ot_precond_solver_chebyshev
     117            2 :          CPASSERT(ASSOCIATED(preconditioner_env%sparse_matrix))
     118              :          CALL estimate_cond_num(preconditioner_env%sparse_matrix, preconditioner_env%condition_num, &
     119              :                                 max_eigenvalue=preconditioner_env%polynomial_max, &
     120            2 :                                 min_eigenvalue=preconditioner_env%polynomial_min)
     121              :          ! Extremal Ritz values can converge from inside the exact spectrum. Halve
     122              :          ! the estimated lower edge as a safety margin and use a rigorous row-sum
     123              :          ! upper bound for the Chebyshev interval.
     124            2 :          preconditioner_env%polynomial_min = 0.5_dp*preconditioner_env%polynomial_min
     125              :          preconditioner_env%polynomial_max = &
     126            2 :             (1.0_dp + 100.0_dp*EPSILON(1.0_dp))*dbcsr_gershgorin_norm(preconditioner_env%sparse_matrix)
     127            2 :          IF (preconditioner_env%polynomial_min <= SQRT(EPSILON(1.0_dp)) .OR. &
     128         3752 :              preconditioner_env%polynomial_max <= preconditioner_env%polynomial_min) THEN
     129            0 :             CPWARN("Invalid Chebyshev bounds; using Cholesky inverse")
     130            0 :             preconditioner_env%solver = ot_precond_solver_inv_chol
     131            0 :             CALL make_full_inverse_cholesky(preconditioner_env, matrix_s)
     132              :          ELSE
     133              :             ! Preserve the SPD operator instead of replacing it with its inverse.
     134              :             CALL transfer_dbcsr_to_fm(preconditioner_env%sparse_matrix, preconditioner_env%fm, &
     135            2 :                                       preconditioner_env%para_env, preconditioner_env%ctxt)
     136              :          END IF
     137              :       CASE (ot_precond_solver_default)
     138         3752 :          preconditioner_env%solver = ot_precond_solver_default
     139              :       CASE DEFAULT
     140              :          !
     141         9592 :          CPABORT("Doesn't know this type of solver")
     142              :       END SELECT
     143              : 
     144         9592 :    END SUBROUTINE solve_preconditioner
     145              : 
     146              : ! **************************************************************************************************
     147              : !> \brief Compute the inverse using cholseky factorization
     148              : !> \param preconditioner_env ...
     149              : !> \param matrix_s ...
     150              : ! **************************************************************************************************
     151        17502 :    SUBROUTINE make_full_inverse_cholesky(preconditioner_env, matrix_s)
     152              : 
     153              :       TYPE(preconditioner_type)                          :: preconditioner_env
     154              :       TYPE(dbcsr_type), OPTIONAL, POINTER                :: matrix_s
     155              : 
     156              :       CHARACTER(len=*), PARAMETER :: routineN = 'make_full_inverse_cholesky'
     157              : 
     158              :       INTEGER                                            :: handle, info
     159              :       TYPE(cp_fm_type)                                   :: fm_work
     160              :       TYPE(cp_fm_type), POINTER                          :: fm
     161              : 
     162         5834 :       CALL timeset(routineN, handle)
     163              : 
     164              :       ! Maybe we will get a sparse Cholesky at a given point then this can go,
     165              :       ! if stuff was stored in fm anyway this simple returns
     166              :       CALL transfer_dbcsr_to_fm(preconditioner_env%sparse_matrix, preconditioner_env%fm, &
     167         5834 :                                 preconditioner_env%para_env, preconditioner_env%ctxt)
     168         5834 :       fm => preconditioner_env%fm
     169              : 
     170         5834 :       CALL cp_fm_create(fm_work, fm%matrix_struct, name="fm_work")
     171              :       !
     172              :       ! compute the inverse of SPD matrix fm using the Cholesky factorization
     173         5834 :       CALL cp_fm_cholesky_decompose(fm, info_out=info)
     174              : 
     175              :       !
     176              :       ! if fm not SPD we go with the overlap matrix
     177         5834 :       IF (info /= 0) THEN
     178              :          !
     179              :          ! just the overlap matrix
     180            0 :          IF (PRESENT(matrix_s)) THEN
     181            0 :             CALL copy_dbcsr_to_fm(matrix_s, fm)
     182            0 :             CALL cp_fm_cholesky_decompose(fm)
     183              :          ELSE
     184            0 :             CALL cp_fm_set_all(fm, alpha=0._dp, beta=1._dp)
     185              :          END IF
     186              :       END IF
     187         5834 :       CALL cp_fm_cholesky_invert(fm)
     188              : 
     189         5834 :       CALL cp_fm_uplo_to_full(fm, fm_work)
     190         5834 :       CALL cp_fm_release(fm_work)
     191              : 
     192         5834 :       CALL timestop(handle)
     193              : 
     194         5834 :    END SUBROUTINE make_full_inverse_cholesky
     195              : 
     196              : ! **************************************************************************************************
     197              : !> \brief Only perform the factorization, can be used later to solve the linear
     198              : !>        system on the fly
     199              : !> \param preconditioner_env ...
     200              : !> \param matrix_s ...
     201              : ! **************************************************************************************************
     202            0 :    SUBROUTINE make_full_fact_cholesky(preconditioner_env, matrix_s)
     203              : 
     204              :       TYPE(preconditioner_type)                          :: preconditioner_env
     205              :       TYPE(dbcsr_type), OPTIONAL, POINTER                :: matrix_s
     206              : 
     207              :       CHARACTER(len=*), PARAMETER :: routineN = 'make_full_fact_cholesky'
     208              : 
     209              :       INTEGER                                            :: handle, info_out
     210              :       TYPE(cp_fm_type), POINTER                          :: fm
     211              : 
     212            0 :       CALL timeset(routineN, handle)
     213              : 
     214              :       ! Maybe we will get a sparse Cholesky at a given point then this can go,
     215              :       ! if stuff was stored in fm anyway this simple returns
     216              :       CALL transfer_dbcsr_to_fm(preconditioner_env%sparse_matrix, preconditioner_env%fm, &
     217            0 :                                 preconditioner_env%para_env, preconditioner_env%ctxt)
     218              : 
     219            0 :       fm => preconditioner_env%fm
     220              :       !
     221              :       ! compute the inverse of SPD matrix fm using the Cholesky factorization
     222            0 :       CALL cp_fm_cholesky_decompose(fm, info_out=info_out)
     223              :       !
     224              :       ! if fm not SPD we go with the overlap matrix
     225            0 :       IF (info_out /= 0) THEN
     226              :          !
     227              :          ! just the overlap matrix
     228            0 :          IF (PRESENT(matrix_s)) THEN
     229            0 :             CALL copy_dbcsr_to_fm(matrix_s, fm)
     230            0 :             CALL cp_fm_cholesky_decompose(fm)
     231              :          ELSE
     232            0 :             CALL cp_fm_set_all(fm, alpha=0._dp, beta=1._dp)
     233              :          END IF
     234              :       END IF
     235              : 
     236            0 :       CALL timestop(handle)
     237              : 
     238            0 :    END SUBROUTINE make_full_fact_cholesky
     239              : 
     240              : ! **************************************************************************************************
     241              : !> \brief computes an approximate inverse using Hotelling iterations
     242              : !> \param preconditioner_env ...
     243              : !> \param matrix_h as S is not always present this is a safe template for the transfer
     244              : ! **************************************************************************************************
     245            4 :    SUBROUTINE make_inverse_update(preconditioner_env, matrix_h)
     246              :       TYPE(preconditioner_type)                          :: preconditioner_env
     247              :       TYPE(dbcsr_type), POINTER                          :: matrix_h
     248              : 
     249              :       CHARACTER(len=*), PARAMETER :: routineN = 'make_inverse_update'
     250              : 
     251              :       INTEGER                                            :: handle
     252              :       LOGICAL                                            :: use_guess
     253              :       REAL(KIND=dp)                                      :: filter_eps
     254              : 
     255            4 :       CALL timeset(routineN, handle)
     256            4 :       use_guess = .TRUE.
     257              :       !
     258              :       ! uses an update of the full inverse (needs to be computed the first time)
     259              :       ! make sure preconditioner_env is not destroyed in between
     260              : 
     261            4 :       CALL cite_reference(Schiffmann2015)
     262              : 
     263              :       ! Maybe I gonna add a fm Hotelling, ... for now the same as above make sure we are dbcsr
     264            4 :       CALL transfer_fm_to_dbcsr(preconditioner_env%fm, preconditioner_env%sparse_matrix, matrix_h)
     265            4 :       IF (.NOT. ASSOCIATED(preconditioner_env%dbcsr_matrix)) THEN
     266            0 :          use_guess = .FALSE.
     267            0 :          CALL dbcsr_init_p(preconditioner_env%dbcsr_matrix)
     268              :          CALL dbcsr_create(preconditioner_env%dbcsr_matrix, "prec_dbcsr", &
     269            0 :                            template=matrix_h, matrix_type=dbcsr_type_no_symmetry)
     270              :       END IF
     271              : 
     272              :       ! Try to get a reasonbale guess for the filtering threshold
     273            4 :       filter_eps = 1.0_dp/preconditioner_env%condition_num*0.1_dp
     274              : 
     275              :       ! Aggressive filtering on the initial guess is needed to avoid fill ins and retain sparsity
     276            4 :       CALL dbcsr_filter(preconditioner_env%dbcsr_matrix, filter_eps*100.0_dp)
     277              :       ! We don't need a high accuracy for the inverse so 0.4 is reasonable for convergence
     278              :       CALL invert_Hotelling(preconditioner_env%dbcsr_matrix, preconditioner_env%sparse_matrix, filter_eps*10.0_dp, &
     279            4 :                             use_inv_as_guess=use_guess, norm_convergence=0.4_dp, filter_eps=filter_eps)
     280              : 
     281            4 :       CALL timestop(handle)
     282              : 
     283            4 :    END SUBROUTINE make_inverse_update
     284              : 
     285              : ! **************************************************************************************************
     286              : !> \brief computes an approximation to the condition number of a matrix using
     287              : !>        arnoldi iterations
     288              : !> \param matrix ...
     289              : !> \param cond_num ...
     290              : !> \param max_eigenvalue ...
     291              : !> \param min_eigenvalue ...
     292              : ! **************************************************************************************************
     293            4 :    SUBROUTINE estimate_cond_num(matrix, cond_num, max_eigenvalue, min_eigenvalue)
     294              :       TYPE(dbcsr_type), POINTER                          :: matrix
     295              :       REAL(KIND=dp)                                      :: cond_num
     296              :       REAL(KIND=dp), INTENT(OUT), OPTIONAL               :: max_eigenvalue, min_eigenvalue
     297              : 
     298              :       CHARACTER(len=*), PARAMETER                        :: routineN = 'estimate_cond_num'
     299              : 
     300              :       INTEGER                                            :: handle
     301              :       REAL(KIND=dp)                                      :: max_ev, min_ev
     302              :       TYPE(arnoldi_env_type)                             :: arnoldi_env
     303            4 :       TYPE(dbcsr_p_type), DIMENSION(:), POINTER          :: matrices
     304              : 
     305            4 :       CALL timeset(routineN, handle)
     306              : 
     307              :       ! its better to do 2 calculations as the maximum should quickly converge and the minimum won't need iram
     308            8 :       ALLOCATE (matrices(1))
     309            4 :       matrices(1)%matrix => matrix
     310              :       ! compute the minimum ev
     311              :       CALL setup_arnoldi_env(arnoldi_env, matrices, max_iter=20, threshold=5.0E-4_dp, selection_crit=2, &
     312            4 :                              nval_request=1, nrestarts=15, generalized_ev=.FALSE., iram=.FALSE.)
     313            4 :       CALL arnoldi_ev(matrices, arnoldi_env)
     314            4 :       max_ev = REAL(get_selected_ritz_val(arnoldi_env, 1), dp)
     315            4 :       CALL deallocate_arnoldi_env(arnoldi_env)
     316              : 
     317              :       CALL setup_arnoldi_env(arnoldi_env, matrices, max_iter=20, threshold=5.0E-4_dp, selection_crit=3, &
     318            4 :                              nval_request=1, nrestarts=15, generalized_ev=.FALSE., iram=.FALSE.)
     319            4 :       CALL arnoldi_ev(matrices, arnoldi_env)
     320            4 :       min_ev = REAL(get_selected_ritz_val(arnoldi_env, 1), dp)
     321            4 :       CALL deallocate_arnoldi_env(arnoldi_env)
     322              : 
     323            4 :       cond_num = max_ev/min_ev
     324            4 :       IF (PRESENT(max_eigenvalue)) max_eigenvalue = max_ev
     325            4 :       IF (PRESENT(min_eigenvalue)) min_eigenvalue = min_ev
     326            4 :       DEALLOCATE (matrices)
     327              : 
     328            4 :       CALL timestop(handle)
     329            4 :    END SUBROUTINE estimate_cond_num
     330              : 
     331              : ! **************************************************************************************************
     332              : !> \brief transfers a full matrix to a dbcsr
     333              : !> \param fm_matrix a full matrix gets deallocated in the end
     334              : !> \param dbcsr_matrix a dbcsr matrix, gets create from a template
     335              : !> \param template_mat the template which is used for the structure
     336              : ! **************************************************************************************************
     337         7924 :    SUBROUTINE transfer_fm_to_dbcsr(fm_matrix, dbcsr_matrix, template_mat)
     338              : 
     339              :       TYPE(cp_fm_type), POINTER                          :: fm_matrix
     340              :       TYPE(dbcsr_type), POINTER                          :: dbcsr_matrix, template_mat
     341              : 
     342              :       CHARACTER(len=*), PARAMETER :: routineN = 'transfer_fm_to_dbcsr'
     343              : 
     344              :       INTEGER                                            :: handle
     345              : 
     346         7924 :       CALL timeset(routineN, handle)
     347         7924 :       IF (ASSOCIATED(fm_matrix)) THEN
     348         7912 :          IF (.NOT. ASSOCIATED(dbcsr_matrix)) THEN
     349         4728 :             CALL dbcsr_init_p(dbcsr_matrix)
     350              :             CALL dbcsr_create(dbcsr_matrix, template=template_mat, &
     351              :                               name="preconditioner_env%dbcsr_matrix", &
     352         4728 :                               matrix_type=dbcsr_type_no_symmetry)
     353              :          END IF
     354         7912 :          CALL copy_fm_to_dbcsr(fm_matrix, dbcsr_matrix)
     355         7912 :          CALL cp_fm_release(fm_matrix)
     356         7912 :          DEALLOCATE (fm_matrix)
     357              :          NULLIFY (fm_matrix)
     358              :       END IF
     359              : 
     360         7924 :       CALL timestop(handle)
     361              : 
     362         7924 :    END SUBROUTINE transfer_fm_to_dbcsr
     363              : 
     364              : ! **************************************************************************************************
     365              : !> \brief transfers a dbcsr to a full matrix
     366              : !> \param dbcsr_matrix a dbcsr matrix, gets deallocated at the end
     367              : !> \param fm_matrix a full matrix gets created if not yet done
     368              : !> \param para_env the para_env
     369              : !> \param context the blacs context
     370              : ! **************************************************************************************************
     371         7512 :    SUBROUTINE transfer_dbcsr_to_fm(dbcsr_matrix, fm_matrix, para_env, context)
     372              : 
     373              :       TYPE(dbcsr_type), POINTER                          :: dbcsr_matrix
     374              :       TYPE(cp_fm_type), POINTER                          :: fm_matrix
     375              :       TYPE(mp_para_env_type), POINTER                    :: para_env
     376              :       TYPE(cp_blacs_env_type), POINTER                   :: context
     377              : 
     378              :       CHARACTER(len=*), PARAMETER :: routineN = 'transfer_dbcsr_to_fm'
     379              : 
     380              :       INTEGER                                            :: handle, n
     381              :       TYPE(cp_fm_struct_type), POINTER                   :: fm_struct_tmp
     382              : 
     383         7512 :       CALL timeset(routineN, handle)
     384         7512 :       IF (ASSOCIATED(dbcsr_matrix)) THEN
     385         5836 :          NULLIFY (fm_struct_tmp)
     386              : 
     387         5836 :          IF (ASSOCIATED(fm_matrix)) THEN
     388            0 :             CALL cp_fm_release(fm_matrix)
     389            0 :             DEALLOCATE (fm_matrix)
     390              :          END IF
     391              : 
     392         5836 :          CALL dbcsr_get_info(dbcsr_matrix, nfullrows_total=n)
     393              :          CALL cp_fm_struct_create(fm_struct_tmp, nrow_global=n, ncol_global=n, &
     394         5836 :                                   context=context, para_env=para_env)
     395         5836 :          ALLOCATE (fm_matrix)
     396         5836 :          CALL cp_fm_create(fm_matrix, fm_struct_tmp)
     397         5836 :          CALL cp_fm_struct_release(fm_struct_tmp)
     398              : 
     399         5836 :          CALL copy_dbcsr_to_fm(dbcsr_matrix, fm_matrix)
     400         5836 :          CALL dbcsr_release(dbcsr_matrix)
     401         5836 :          DEALLOCATE (dbcsr_matrix)
     402              :       END IF
     403              : 
     404         7512 :       CALL timestop(handle)
     405              : 
     406         7512 :    END SUBROUTINE transfer_dbcsr_to_fm
     407              : 
     408              : END MODULE preconditioner_solvers
        

Generated by: LCOV version 2.0-1