LCOV - code coverage report
Current view: top level - src - pao_optimizer.F (source / functions) Hit Total Coverage
Test: CP2K Regtests (git:3130539) Lines: 123 128 96.1 %
Date: 2025-05-14 06:57:18 Functions: 7 7 100.0 %

          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 Optimizers used by pao_main.F
      10             : !> \author Ole Schuett
      11             : ! **************************************************************************************************
      12             : MODULE pao_optimizer
      13             :    USE arnoldi_api,                     ONLY: arnoldi_extremal
      14             :    USE cp_dbcsr_api,                    ONLY: &
      15             :         dbcsr_add, dbcsr_copy, dbcsr_create, dbcsr_get_info, dbcsr_multiply, dbcsr_release, &
      16             :         dbcsr_scale, dbcsr_set, dbcsr_type
      17             :    USE cp_dbcsr_contrib,                ONLY: dbcsr_add_on_diag,&
      18             :                                               dbcsr_dot,&
      19             :                                               dbcsr_frobenius_norm,&
      20             :                                               dbcsr_reserve_diag_blocks
      21             :    USE kinds,                           ONLY: dp
      22             :    USE pao_input,                       ONLY: pao_opt_bfgs,&
      23             :                                               pao_opt_cg
      24             :    USE pao_types,                       ONLY: pao_env_type
      25             : #include "./base/base_uses.f90"
      26             : 
      27             :    IMPLICIT NONE
      28             : 
      29             :    PRIVATE
      30             : 
      31             :    PUBLIC :: pao_opt_init, pao_opt_finalize, pao_opt_new_dir
      32             : 
      33             : CONTAINS
      34             : 
      35             : ! **************************************************************************************************
      36             : !> \brief Initialize the optimizer
      37             : !> \param pao ...
      38             : ! **************************************************************************************************
      39         246 :    SUBROUTINE pao_opt_init(pao)
      40             :       TYPE(pao_env_type), POINTER                        :: pao
      41             : 
      42         246 :       CALL dbcsr_copy(pao%matrix_D, pao%matrix_G)
      43         246 :       CALL dbcsr_set(pao%matrix_D, 0.0_dp)
      44             : 
      45         246 :       CALL dbcsr_copy(pao%matrix_G_prev, pao%matrix_D)
      46             : 
      47         246 :       IF (pao%precondition) THEN
      48          82 :          CALL dbcsr_copy(pao%matrix_D_preconed, pao%matrix_D)
      49             :       END IF
      50             : 
      51         246 :       IF (pao%optimizer == pao_opt_bfgs) &
      52          12 :          CALL pao_opt_init_bfgs(pao)
      53             : 
      54         246 :    END SUBROUTINE pao_opt_init
      55             : 
      56             : ! **************************************************************************************************
      57             : !> \brief Initialize the BFGS optimizer
      58             : !> \param pao ...
      59             : ! **************************************************************************************************
      60          12 :    SUBROUTINE pao_opt_init_bfgs(pao)
      61             :       TYPE(pao_env_type), POINTER                        :: pao
      62             : 
      63          12 :       INTEGER, DIMENSION(:), POINTER                     :: nparams
      64             : 
      65          12 :       CALL dbcsr_get_info(pao%matrix_X, row_blk_size=nparams)
      66             : 
      67             :       CALL dbcsr_create(pao%matrix_BFGS, &
      68             :                         template=pao%matrix_X, &
      69             :                         row_blk_size=nparams, &
      70             :                         col_blk_size=nparams, &
      71          12 :                         name="PAO matrix_BFGS")
      72             : 
      73          12 :       CALL dbcsr_reserve_diag_blocks(pao%matrix_BFGS)
      74          12 :       CALL dbcsr_set(pao%matrix_BFGS, 0.0_dp)
      75          12 :       CALL dbcsr_add_on_diag(pao%matrix_BFGS, 1.0_dp)
      76             : 
      77          12 :    END SUBROUTINE pao_opt_init_bfgs
      78             : 
      79             : ! **************************************************************************************************
      80             : !> \brief Finalize the optimizer
      81             : !> \param pao ...
      82             : ! **************************************************************************************************
      83         246 :    SUBROUTINE pao_opt_finalize(pao)
      84             :       TYPE(pao_env_type), POINTER                        :: pao
      85             : 
      86         246 :       CALL dbcsr_release(pao%matrix_D)
      87         246 :       CALL dbcsr_release(pao%matrix_G_prev)
      88         246 :       IF (pao%precondition) &
      89          82 :          CALL dbcsr_release(pao%matrix_D_preconed)
      90             : 
      91         246 :       IF (pao%optimizer == pao_opt_bfgs) &
      92          12 :          CALL dbcsr_release(pao%matrix_BFGS)
      93             : 
      94         246 :    END SUBROUTINE pao_opt_finalize
      95             : 
      96             : ! **************************************************************************************************
      97             : !> \brief Calculates the new search direction.
      98             : !> \param pao ...
      99             : !> \param icycle ...
     100             : ! **************************************************************************************************
     101        2616 :    SUBROUTINE pao_opt_new_dir(pao, icycle)
     102             :       TYPE(pao_env_type), POINTER                        :: pao
     103             :       INTEGER, INTENT(IN)                                :: icycle
     104             : 
     105             :       CHARACTER(len=*), PARAMETER                        :: routineN = 'pao_opt_new_dir'
     106             : 
     107             :       INTEGER                                            :: handle
     108             :       TYPE(dbcsr_type)                                   :: matrix_G_preconed
     109             : 
     110        2616 :       CALL timeset(routineN, handle)
     111             : 
     112        2616 :       IF (pao%precondition) THEN
     113             :          ! We can't convert matrix_D for and back every time, the numeric noise would disturb the CG,
     114             :          ! hence we keep matrix_D_preconed around.
     115        1290 :          CALL dbcsr_copy(matrix_G_preconed, pao%matrix_G)
     116             :          CALL dbcsr_multiply("N", "N", 1.0_dp, pao%matrix_precon, pao%matrix_G, &
     117        1290 :                              0.0_dp, matrix_G_preconed, retain_sparsity=.TRUE.)
     118        1290 :          CALL pao_opt_new_dir_low(pao, icycle, matrix_G_preconed, pao%matrix_G_prev, pao%matrix_D_preconed)
     119             :          CALL dbcsr_multiply("N", "N", 1.0_dp, pao%matrix_precon, pao%matrix_D_preconed, &
     120        1290 :                              0.0_dp, pao%matrix_D, retain_sparsity=.TRUE.)
     121             : 
     122             :          ! store preconditioned gradient for next iteration
     123        1290 :          CALL dbcsr_copy(pao%matrix_G_prev, matrix_G_preconed)
     124             : 
     125        1290 :          pao%norm_G = dbcsr_frobenius_norm(matrix_G_preconed)
     126        1290 :          IF (pao%iw > 0) WRITE (pao%iw, *) "PAO| norm of preconditioned gradient:", pao%norm_G
     127        1290 :          CALL dbcsr_release(matrix_G_preconed)
     128             : 
     129             :       ELSE
     130        1326 :          CALL pao_opt_new_dir_low(pao, icycle, pao%matrix_G, pao%matrix_G_prev, pao%matrix_D)
     131        1326 :          CALL dbcsr_copy(pao%matrix_G_prev, pao%matrix_G) ! store gradient for next iteration
     132        1326 :          pao%norm_G = dbcsr_frobenius_norm(pao%matrix_G)
     133        1326 :          IF (pao%iw > 0) WRITE (pao%iw, *) "PAO| norm of gradient:", pao%norm_G
     134             :       END IF
     135             : 
     136        2616 :       CALL timestop(handle)
     137             : 
     138        2616 :    END SUBROUTINE pao_opt_new_dir
     139             : 
     140             : ! **************************************************************************************************
     141             : !> \brief Calculates the new search direction.
     142             : !> \param pao ...
     143             : !> \param icycle ...
     144             : !> \param matrix_G ...
     145             : !> \param matrix_G_prev ...
     146             : !> \param matrix_D ...
     147             : ! **************************************************************************************************
     148        2616 :    SUBROUTINE pao_opt_new_dir_low(pao, icycle, matrix_G, matrix_G_prev, matrix_D)
     149             :       TYPE(pao_env_type), POINTER                        :: pao
     150             :       INTEGER, INTENT(IN)                                :: icycle
     151             :       TYPE(dbcsr_type)                                   :: matrix_G, matrix_G_prev, matrix_D
     152             : 
     153        4944 :       SELECT CASE (pao%optimizer)
     154             :       CASE (pao_opt_cg)
     155        2328 :          CALL pao_opt_newdir_cg(pao, icycle, matrix_G, matrix_G_prev, matrix_D)
     156             :       CASE (pao_opt_bfgs)
     157         288 :          CALL pao_opt_newdir_bfgs(pao, icycle, matrix_G, matrix_G_prev, matrix_D)
     158             :       CASE DEFAULT
     159        2616 :          CPABORT("PAO: unknown optimizer")
     160             :       END SELECT
     161             : 
     162        2616 :    END SUBROUTINE pao_opt_new_dir_low
     163             : 
     164             : ! **************************************************************************************************
     165             : !> \brief Conjugate Gradient algorithm
     166             : !> \param pao ...
     167             : !> \param icycle ...
     168             : !> \param matrix_G ...
     169             : !> \param matrix_G_prev ...
     170             : !> \param matrix_D ...
     171             : ! **************************************************************************************************
     172        2328 :    SUBROUTINE pao_opt_newdir_cg(pao, icycle, matrix_G, matrix_G_prev, matrix_D)
     173             :       TYPE(pao_env_type), POINTER                        :: pao
     174             :       INTEGER, INTENT(IN)                                :: icycle
     175             :       TYPE(dbcsr_type)                                   :: matrix_G, matrix_G_prev, matrix_D
     176             : 
     177             :       REAL(KIND=dp)                                      :: beta, change, trace_D, trace_D_Gnew, &
     178             :                                                             trace_G_mix, trace_G_new, trace_G_prev
     179             : 
     180             :       ! determine CG mixing factor
     181        2328 :       IF (icycle <= pao%cg_init_steps) THEN
     182         444 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| warming up with steepest descent"
     183         444 :          beta = 0.0_dp
     184             :       ELSE
     185        1884 :          CALL dbcsr_dot(matrix_G, matrix_G, trace_G_new)
     186        1884 :          CALL dbcsr_dot(matrix_G_prev, matrix_G_prev, trace_G_prev)
     187        1884 :          CALL dbcsr_dot(matrix_G, matrix_G_prev, trace_G_mix)
     188        1884 :          CALL dbcsr_dot(matrix_D, matrix_G, trace_D_Gnew)
     189        1884 :          CALL dbcsr_dot(matrix_D, matrix_D, trace_D)
     190        1884 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| trace_G_new ", trace_G_new
     191        1884 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| trace_G_prev ", trace_G_prev
     192        1884 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| trace_G_mix ", trace_G_mix
     193        1884 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| trace_D ", trace_D
     194        1884 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| trace_D_Gnew", trace_D_Gnew
     195             : 
     196        1884 :          IF (trace_G_prev /= 0.0_dp) THEN
     197        1884 :             beta = (trace_G_new - trace_G_mix)/trace_G_prev !Polak-Ribiere
     198             :          END IF
     199             : 
     200        1884 :          IF (beta < 0.0_dp) THEN
     201          78 :             IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| resetting because beta < 0"
     202          78 :             beta = 0.0_dp
     203             :          END IF
     204             : 
     205        1884 :          change = trace_D_Gnew**2/trace_D*trace_G_new
     206        1884 :          IF (change > pao%cg_reset_limit) THEN
     207           0 :             IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| resetting because change > CG_RESET_LIMIT"
     208           0 :             beta = 0.0_dp
     209             :          END IF
     210             : 
     211             :       END IF
     212             : 
     213        2328 :       IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|CG| beta: ", beta
     214             : 
     215             :       ! calculate new CG direction matrix_D
     216        2328 :       CALL dbcsr_add(matrix_D, matrix_G, beta, -1.0_dp)
     217             : 
     218        2328 :    END SUBROUTINE pao_opt_newdir_cg
     219             : 
     220             : ! **************************************************************************************************
     221             : !> \brief Broyden-Fletcher-Goldfarb-Shanno algorithm
     222             : !> \param pao ...
     223             : !> \param icycle ...
     224             : !> \param matrix_G ...
     225             : !> \param matrix_G_prev ...
     226             : !> \param matrix_D ...
     227             : ! **************************************************************************************************
     228         288 :    SUBROUTINE pao_opt_newdir_bfgs(pao, icycle, matrix_G, matrix_G_prev, matrix_D)
     229             :       TYPE(pao_env_type), POINTER                        :: pao
     230             :       INTEGER, INTENT(IN)                                :: icycle
     231             :       TYPE(dbcsr_type)                                   :: matrix_G, matrix_G_prev, matrix_D
     232             : 
     233             :       CHARACTER(len=*), PARAMETER :: routineN = 'pao_opt_newdir_bfgs'
     234             : 
     235             :       INTEGER                                            :: handle
     236             :       LOGICAL                                            :: arnoldi_converged
     237             :       REAL(dp)                                           :: eval_max, eval_min, theta, trace_ry, &
     238             :                                                             trace_sy, trace_yHy, trace_yy
     239             :       TYPE(dbcsr_type)                                   :: matrix_Hy, matrix_Hyr, matrix_r, &
     240             :                                                             matrix_rr, matrix_ryH, matrix_ryHyr, &
     241             :                                                             matrix_s, matrix_y, matrix_yr
     242             : 
     243         288 :       CALL timeset(routineN, handle)
     244             : 
     245             :       !TODO add filtering?
     246             : 
     247             :       ! Notation according to the book from Nocedal and Wright, see chapter 6.
     248         288 :       IF (icycle > 1) THEN
     249             :          ! y = G - G_prev
     250         276 :          CALL dbcsr_copy(matrix_y, matrix_G)
     251         276 :          CALL dbcsr_add(matrix_y, matrix_G_prev, 1.0_dp, -1.0_dp) ! dG
     252             : 
     253             :          ! s = X - X_prev
     254         276 :          CALL dbcsr_copy(matrix_s, matrix_D)
     255         276 :          CALL dbcsr_scale(matrix_s, pao%linesearch%step_size) ! dX
     256             : 
     257             :          ! sy = MATMUL(TRANPOSE(s), y)
     258         276 :          CALL dbcsr_dot(matrix_s, matrix_y, trace_sy)
     259             : 
     260             :          ! heuristic initialization
     261         276 :          IF (icycle == 2) THEN
     262          10 :             CALL dbcsr_dot(matrix_Y, matrix_Y, trace_yy)
     263          10 :             CALL dbcsr_scale(pao%matrix_BFGS, trace_sy/trace_yy)
     264          10 :             IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|BFGS| Initializing with:", trace_sy/trace_yy
     265             :          END IF
     266             : 
     267             :          ! Hy = MATMUL(H, y)
     268         276 :          CALL dbcsr_create(matrix_Hy, template=matrix_G, matrix_type="N")
     269         276 :          CALL dbcsr_multiply("N", "N", 1.0_dp, pao%matrix_BFGS, matrix_y, 0.0_dp, matrix_Hy)
     270             : 
     271             :          ! yHy = MATMUL(TRANPOSE(y), Hy)
     272         276 :          CALL dbcsr_dot(matrix_y, matrix_Hy, trace_yHy)
     273             : 
     274             :          ! Use damped BFGS algorithm to ensure H remains positive definite.
     275             :          ! See chapter 18 in Nocedal and Wright's book for details.
     276             :          ! The formulas were adopted to inverse Hessian algorithm.
     277         276 :          IF (trace_sy < 0.2_dp*trace_yHy) THEN
     278           0 :             theta = 0.8_dp*trace_yHy/(trace_yHy - trace_sy)
     279           0 :             IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|BFGS| Dampening theta:", theta
     280             :          ELSE
     281         276 :             theta = 1.0
     282             :          END IF
     283             : 
     284             :          ! r = theta*s + (1-theta)*Hy
     285         276 :          CALL dbcsr_copy(matrix_r, matrix_s)
     286         276 :          CALL dbcsr_add(matrix_r, matrix_Hy, theta, (1.0_dp - theta))
     287             : 
     288             :          ! use t instead of y to update B matrix
     289         276 :          CALL dbcsr_dot(matrix_r, matrix_y, trace_ry)
     290         276 :          CPASSERT(trace_RY > 0.0_dp)
     291             : 
     292             :          ! yr = MATMUL(y, TRANSPOSE(r))
     293         276 :          CALL dbcsr_create(matrix_yr, template=pao%matrix_BFGS, matrix_type="N")
     294         276 :          CALL dbcsr_multiply("N", "T", 1.0_dp, matrix_y, matrix_r, 0.0_dp, matrix_yr)
     295             : 
     296             :          ! Hyr = MATMUL(H, yr)
     297         276 :          CALL dbcsr_create(matrix_Hyr, template=pao%matrix_BFGS, matrix_type="N")
     298         276 :          CALL dbcsr_multiply("N", "N", 1.0_dp, pao%matrix_BFGS, matrix_yr, 0.0_dp, matrix_Hyr)
     299             : 
     300             :          ! ryH = MATMUL(TRANSPOSE(yr), H)
     301         276 :          CALL dbcsr_create(matrix_ryH, template=pao%matrix_BFGS, matrix_type="N")
     302         276 :          CALL dbcsr_multiply("T", "N", 1.0_dp, matrix_yr, pao%matrix_BFGS, 0.0_dp, matrix_ryH)
     303             : 
     304             :          ! ryHry = MATMUL(ryH,yr)
     305         276 :          CALL dbcsr_create(matrix_ryHyr, template=pao%matrix_BFGS, matrix_type="N")
     306         276 :          CALL dbcsr_multiply("N", "N", 1.0_dp, matrix_ryH, matrix_yr, 0.0_dp, matrix_ryHyr)
     307             : 
     308             :          ! rr = MATMUL(r,TRANSPOSE(r))
     309         276 :          CALL dbcsr_create(matrix_rr, template=pao%matrix_BFGS, matrix_type="N")
     310         276 :          CALL dbcsr_multiply("N", "T", 1.0_dp, matrix_r, matrix_r, 0.0_dp, matrix_rr)
     311             : 
     312             :          ! H = H - Hyr/ry - ryH/ry + ryHyr/(ry**2) + rr/ry
     313         276 :          CALL dbcsr_add(pao%matrix_BFGS, matrix_HYR, 1.0_dp, -1.0_dp/trace_ry)
     314         276 :          CALL dbcsr_add(pao%matrix_BFGS, matrix_ryH, 1.0_dp, -1.0_dp/trace_ry)
     315         276 :          CALL dbcsr_add(pao%matrix_BFGS, matrix_ryHyr, 1.0_dp, +1.0_dp/(trace_ry**2))
     316         276 :          CALL dbcsr_add(pao%matrix_BFGS, matrix_rr, 1.0_dp, +1.0_dp/trace_ry)
     317             : 
     318             :          ! clean up
     319         276 :          CALL dbcsr_release(matrix_y)
     320         276 :          CALL dbcsr_release(matrix_s)
     321         276 :          CALL dbcsr_release(matrix_r)
     322         276 :          CALL dbcsr_release(matrix_Hy)
     323         276 :          CALL dbcsr_release(matrix_yr)
     324         276 :          CALL dbcsr_release(matrix_Hyr)
     325         276 :          CALL dbcsr_release(matrix_ryH)
     326         276 :          CALL dbcsr_release(matrix_ryHyr)
     327         276 :          CALL dbcsr_release(matrix_rr)
     328             :       END IF
     329             : 
     330             :       ! approximate condition of Hessian
     331             :       !TODO: good setting for arnoldi?
     332             :       CALL arnoldi_extremal(pao%matrix_BFGS, eval_max, eval_min, max_iter=100, &
     333         288 :                             threshold=1e-2_dp, converged=arnoldi_converged)
     334         288 :       IF (arnoldi_converged) THEN
     335         432 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|BFGS| evals of inv. Hessian: min, max, max/min", &
     336         288 :             eval_min, eval_max, eval_max/eval_min
     337             :       ELSE
     338           0 :          IF (pao%iw_opt > 0) WRITE (pao%iw_opt, *) "PAO|BFGS| arnoldi of inv. Hessian did not converged."
     339             :       END IF
     340             : 
     341             :       ! calculate new direction
     342             :       ! d = MATMUL(H, -g)
     343             :       CALL dbcsr_multiply("N", "N", -1.0_dp, pao%matrix_BFGS, matrix_G, &
     344         288 :                           0.0_dp, matrix_D, retain_sparsity=.TRUE.)
     345             : 
     346         288 :       CALL timestop(handle)
     347         288 :    END SUBROUTINE pao_opt_newdir_bfgs
     348             : 
     349             : END MODULE pao_optimizer

Generated by: LCOV version 1.15