LCOV - code coverage report
Current view: top level - src - ot_covariant_preconditioner_unittest.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:92574dc) Lines: 96.6 % 118 114
Test Date: 2026-09-24 01:27:39 Functions: 100.0 % 9 9

            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            2 : PROGRAM ot_covariant_preconditioner_unittest
       8            2 :    USE cp_blacs_env,                    ONLY: cp_blacs_env_create,&
       9              :                                               cp_blacs_env_release,&
      10              :                                               cp_blacs_env_type
      11              :    USE cp_fm_struct,                    ONLY: cp_fm_struct_create,&
      12              :                                               cp_fm_struct_release,&
      13              :                                               cp_fm_struct_type
      14              :    USE cp_fm_types,                     ONLY: cp_fm_create,&
      15              :                                               cp_fm_get_submatrix,&
      16              :                                               cp_fm_release,&
      17              :                                               cp_fm_set_submatrix,&
      18              :                                               cp_fm_type
      19              :    USE input_constants,                 ONLY: ot_precond_full_all_covariant
      20              :    USE iso_fortran_env,                 ONLY: error_unit
      21              :    USE kinds,                           ONLY: dp
      22              :    USE message_passing,                 ONLY: mp_para_env_type,&
      23              :                                               mp_world_finalize,&
      24              :                                               mp_world_init
      25              :    USE preconditioner_apply,            ONLY: apply_preconditioner_fm
      26              :    USE preconditioner_types,            ONLY: destroy_preconditioner,&
      27              :                                               init_preconditioner,&
      28              :                                               preconditioner_type
      29              : 
      30              :    IMPLICIT NONE
      31              : 
      32              :    INTEGER, PARAMETER                                  :: k = 3, n = 5
      33              :    REAL(KIND=dp), PARAMETER                            :: eps = 2.0E-11_dp
      34              : 
      35              :    INTEGER                                             :: i, j
      36              :    REAL(KIND=dp), DIMENSION(n, k)                      :: gradient, gradient_rotated, hessian_x, &
      37              :                                                           output, output_reference, &
      38              :                                                           output_rotated, x_reference
      39              :    REAL(KIND=dp), DIMENSION(n)                         :: full_evals
      40              :    REAL(KIND=dp), DIMENSION(k)                         :: occ_evals
      41              :    REAL(KIND=dp), DIMENSION(k, k)                      :: occupied_h, q, rotation, rotation_2, &
      42              :                                                           rotation_gauge
      43              :    TYPE(cp_blacs_env_type), POINTER                    :: blacs_env
      44              :    TYPE(cp_fm_struct_type), POINTER                    :: fm_struct
      45              :    TYPE(cp_fm_type)                                    :: matrix_in, matrix_out
      46              :    TYPE(mp_para_env_type), POINTER                     :: para_env
      47            8 :    TYPE(preconditioner_type)                           :: preconditioner_env
      48              : 
      49            2 :    NULLIFY (blacs_env, fm_struct, para_env)
      50            2 :    ALLOCATE (para_env)
      51            2 :    CALL mp_world_init(para_env)
      52            2 :    CALL cp_blacs_env_create(blacs_env=blacs_env, para_env=para_env)
      53            2 :    CALL init_preconditioner(preconditioner_env, para_env, blacs_env)
      54              : 
      55              :    CALL cp_fm_struct_create(fm_struct, nrow_global=n, ncol_global=k, &
      56            2 :                             context=blacs_env, para_env=para_env)
      57            2 :    CALL cp_fm_create(matrix_in, fm_struct, name="covariant test input")
      58            2 :    CALL cp_fm_create(matrix_out, fm_struct, name="covariant test output")
      59            2 :    CALL cp_fm_struct_release(fm_struct)
      60              : 
      61              :    CALL cp_fm_struct_create(fm_struct, nrow_global=n, ncol_global=n, &
      62            2 :                             context=blacs_env, para_env=para_env)
      63            2 :    ALLOCATE (preconditioner_env%fm)
      64            2 :    CALL cp_fm_create(preconditioner_env%fm, fm_struct, name="spectral eigenvectors")
      65            2 :    CALL cp_fm_struct_release(fm_struct)
      66              : 
      67              :    CALL cp_fm_struct_create(fm_struct, nrow_global=k, ncol_global=k, &
      68            2 :                             context=blacs_env, para_env=para_env)
      69            2 :    ALLOCATE (preconditioner_env%occ_rotation)
      70            2 :    CALL cp_fm_create(preconditioner_env%occ_rotation, fm_struct, name="occupied rotation")
      71            2 :    CALL cp_fm_struct_release(fm_struct)
      72              : 
      73            2 :    preconditioner_env%in_use = ot_precond_full_all_covariant
      74            2 :    preconditioner_env%energy_gap = 0.10_dp
      75            2 :    ALLOCATE (preconditioner_env%full_evals(n), preconditioner_env%occ_evals(k))
      76            2 :    full_evals = [0.8_dp, 1.3_dp, 2.1_dp, 3.0_dp, 4.4_dp]
      77            2 :    occ_evals = [-0.9_dp, -0.2_dp, 0.35_dp]
      78           12 :    preconditioner_env%full_evals = full_evals
      79            8 :    preconditioner_env%occ_evals = occ_evals
      80              : 
      81            2 :    CALL set_identity(preconditioner_env%fm, n)
      82            2 :    CALL make_rotation(0.43_dp, -0.31_dp, rotation)
      83            2 :    CALL cp_fm_set_submatrix(preconditioner_env%occ_rotation, rotation)
      84              : 
      85            8 :    DO j = 1, k
      86           38 :       DO i = 1, n
      87              :          gradient(i, j) = SIN(0.37_dp*REAL(2*i + j, dp)) + &
      88           30 :                           0.2_dp*COS(0.19_dp*REAL(i - 3*j, dp))
      89              :          x_reference(i, j) = COS(0.23_dp*REAL(i + 2*j, dp)) - &
      90           36 :                              0.1_dp*SIN(0.41_dp*REAL(3*i - j, dp))
      91              :       END DO
      92              :    END DO
      93              : 
      94              :    ! Check the production FM wrapper against its explicit spectral formula.
      95              :    CALL apply_model(gradient, rotation, full_evals, occ_evals, &
      96            2 :                     preconditioner_env%energy_gap, output_reference)
      97            2 :    CALL apply_fm(preconditioner_env, matrix_in, matrix_out, gradient, output)
      98            2 :    CALL assert_close(output, output_reference, eps, "Explicit covariant spectral inverse")
      99              : 
     100              :    ! Invert the frozen-H Sylvester operator A X - X B exactly.
     101            2 :    occupied_h = 0.0_dp
     102            8 :    DO j = 1, k
     103           80 :       occupied_h = occupied_h + occ_evals(j)*outer_product(rotation(:, j), rotation(:, j))
     104              :    END DO
     105          152 :    hessian_x = -MATMUL(x_reference, occupied_h)
     106           12 :    DO i = 1, n
     107           42 :       hessian_x(i, :) = hessian_x(i, :) + full_evals(i)*x_reference(i, :)
     108              :    END DO
     109            2 :    CALL apply_fm(preconditioner_env, matrix_in, matrix_out, hessian_x, output)
     110            2 :    CALL assert_close(output, x_reference, eps, "Frozen-H Sylvester inverse")
     111              : 
     112              :    ! Rotate the occupied gauge and verify P_(C R)(G R) = P_C(G) R.
     113            2 :    CALL make_rotation(-0.37_dp, 0.28_dp, rotation_gauge)
     114           80 :    rotation_2 = MATMUL(TRANSPOSE(rotation_gauge), rotation)
     115          116 :    gradient_rotated = MATMUL(gradient, rotation_gauge)
     116            2 :    CALL cp_fm_set_submatrix(preconditioner_env%occ_rotation, rotation_2)
     117            2 :    CALL apply_fm(preconditioner_env, matrix_in, matrix_out, gradient_rotated, output_rotated)
     118              :    CALL assert_close(output_rotated, MATMUL(output_reference, rotation_gauge), eps, &
     119          116 :                      "Occupied-gauge covariance")
     120              : 
     121              :    ! Eigenvectors may rotate freely inside a degenerate occupied eigenspace.
     122            2 :    occ_evals = [-0.4_dp, -0.4_dp, 0.2_dp]
     123            8 :    preconditioner_env%occ_evals = occ_evals
     124            2 :    CALL cp_fm_set_submatrix(preconditioner_env%occ_rotation, rotation)
     125            2 :    CALL apply_fm(preconditioner_env, matrix_in, matrix_out, gradient, output)
     126            2 :    CALL plane_rotation(0.61_dp, 1, 2, q)
     127           80 :    rotation_2 = MATMUL(rotation, q)
     128            2 :    CALL cp_fm_set_submatrix(preconditioner_env%occ_rotation, rotation_2)
     129            2 :    CALL apply_fm(preconditioner_env, matrix_in, matrix_out, gradient, output_rotated)
     130            2 :    CALL assert_close(output_rotated, output, eps, "Degenerate occupied subspace")
     131              : 
     132              :    ! The gap floor must leave the inverse positive definite.
     133            2 :    preconditioner_env%energy_gap = 0.75_dp
     134            8 :    preconditioner_env%occ_evals = [0.7_dp, 0.9_dp, 1.1_dp]
     135            2 :    CALL cp_fm_set_submatrix(preconditioner_env%occ_rotation, rotation)
     136            2 :    CALL apply_fm(preconditioner_env, matrix_in, matrix_out, gradient, output)
     137           38 :    IF (SUM(gradient*output) <= 0.0_dp) THEN
     138            0 :       ERROR STOP "Gap-floor inverse is not positive definite"
     139              :    END IF
     140              : 
     141            2 :    CALL cp_fm_release(matrix_in)
     142            2 :    CALL cp_fm_release(matrix_out)
     143            2 :    CALL destroy_preconditioner(preconditioner_env)
     144            2 :    CALL cp_blacs_env_release(blacs_env)
     145            2 :    CALL mp_world_finalize()
     146            4 :    DEALLOCATE (para_env)
     147              : 
     148              : CONTAINS
     149              : 
     150              : ! **************************************************************************************************
     151              : !> \brief Apply the production FM path to a replicated test matrix.
     152              : !> \param preconditioner_env ...
     153              : !> \param matrix_in ...
     154              : !> \param matrix_out ...
     155              : !> \param input ...
     156              : !> \param RESULT ...
     157              : ! **************************************************************************************************
     158           12 :    SUBROUTINE apply_fm(preconditioner_env, matrix_in, matrix_out, input, RESULT)
     159              : 
     160              :       TYPE(preconditioner_type)                          :: preconditioner_env
     161              :       TYPE(cp_fm_type), INTENT(IN)                       :: matrix_in, matrix_out
     162              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: input
     163              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: result
     164              : 
     165           12 :       CALL cp_fm_set_submatrix(matrix_in, input)
     166           12 :       CALL apply_preconditioner_fm(preconditioner_env, matrix_in, matrix_out)
     167           12 :       CALL cp_fm_get_submatrix(matrix_out, RESULT)
     168              : 
     169            2 :    END SUBROUTINE apply_fm
     170              : 
     171              : ! **************************************************************************************************
     172              : !> \brief Explicit reference for the covariant state-selective spectral inverse.
     173              : !> \param input ...
     174              : !> \param rotation ...
     175              : !> \param full_evals ...
     176              : !> \param occ_evals ...
     177              : !> \param gap ...
     178              : !> \param RESULT ...
     179              : ! **************************************************************************************************
     180            2 :    SUBROUTINE apply_model(input, rotation, full_evals, occ_evals, gap, RESULT)
     181              : 
     182              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: input, rotation
     183              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: full_evals, occ_evals
     184              :       REAL(KIND=dp), INTENT(IN)                          :: gap
     185              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: result
     186              : 
     187              :       INTEGER                                            :: i, j
     188              :       REAL(KIND=dp), &
     189            4 :          DIMENSION(SIZE(input, 1), SIZE(input, 2))       :: canonical
     190              : 
     191          156 :       canonical = MATMUL(input, rotation)
     192            8 :       DO j = 1, SIZE(input, 2)
     193           38 :          DO i = 1, SIZE(input, 1)
     194           36 :             canonical(i, j) = canonical(i, j)/MAX(gap, full_evals(i) - occ_evals(j))
     195              :          END DO
     196              :       END DO
     197          154 :       RESULT = MATMUL(canonical, TRANSPOSE(rotation))
     198              : 
     199            2 :    END SUBROUTINE apply_model
     200              : 
     201              : ! **************************************************************************************************
     202              : !> \brief Create a product of two plane rotations.
     203              : !> \param angle_12 ...
     204              : !> \param angle_23 ...
     205              : !> \param rotation ...
     206              : ! **************************************************************************************************
     207            4 :    SUBROUTINE make_rotation(angle_12, angle_23, rotation)
     208              : 
     209              :       REAL(KIND=dp), INTENT(IN)                          :: angle_12, angle_23
     210              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: rotation
     211              : 
     212              :       REAL(KIND=dp), &
     213            8 :          DIMENSION(SIZE(rotation, 1), SIZE(rotation, 2)) :: r12, r23
     214              : 
     215            4 :       CALL plane_rotation(angle_12, 1, 2, r12)
     216            4 :       CALL plane_rotation(angle_23, 2, 3, r23)
     217          216 :       rotation = MATMUL(r12, r23)
     218              : 
     219            4 :    END SUBROUTINE make_rotation
     220              : 
     221              : ! **************************************************************************************************
     222              : !> \brief Create an orthogonal plane rotation.
     223              : !> \param angle ...
     224              : !> \param axis_1 ...
     225              : !> \param axis_2 ...
     226              : !> \param rotation ...
     227              : ! **************************************************************************************************
     228           10 :    SUBROUTINE plane_rotation(angle, axis_1, axis_2, rotation)
     229              : 
     230              :       REAL(KIND=dp), INTENT(IN)                          :: angle
     231              :       INTEGER, INTENT(IN)                                :: axis_1, axis_2
     232              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT)        :: rotation
     233              : 
     234              :       INTEGER                                            :: i
     235              : 
     236          130 :       rotation = 0.0_dp
     237           40 :       DO i = 1, SIZE(rotation, 1)
     238           40 :          rotation(i, i) = 1.0_dp
     239              :       END DO
     240           10 :       rotation(axis_1, axis_1) = COS(angle)
     241           10 :       rotation(axis_2, axis_2) = COS(angle)
     242           10 :       rotation(axis_1, axis_2) = -SIN(angle)
     243           10 :       rotation(axis_2, axis_1) = SIN(angle)
     244              : 
     245           10 :    END SUBROUTINE plane_rotation
     246              : 
     247              : ! **************************************************************************************************
     248              : !> \brief Set a distributed full matrix to the identity.
     249              : !> \param matrix ...
     250              : !> \param n ...
     251              : ! **************************************************************************************************
     252            2 :    SUBROUTINE set_identity(matrix, n)
     253              : 
     254              :       TYPE(cp_fm_type), INTENT(IN)                       :: matrix
     255              :       INTEGER, INTENT(IN)                                :: n
     256              : 
     257              :       INTEGER                                            :: i
     258            2 :       REAL(KIND=dp), DIMENSION(n, n)                     :: identity
     259              : 
     260           62 :       identity = 0.0_dp
     261           12 :       DO i = 1, n
     262           12 :          identity(i, i) = 1.0_dp
     263              :       END DO
     264            2 :       CALL cp_fm_set_submatrix(matrix, identity)
     265              : 
     266            2 :    END SUBROUTINE set_identity
     267              : 
     268              : ! **************************************************************************************************
     269              : !> \brief Form a real outer product.
     270              : !> \param left ...
     271              : !> \param right ...
     272              : !> \return ...
     273              : ! **************************************************************************************************
     274            6 :    PURE FUNCTION outer_product(left, right) RESULT(product)
     275              : 
     276              :       REAL(KIND=dp), DIMENSION(:), INTENT(IN)            :: left, right
     277              :       REAL(KIND=dp), DIMENSION(SIZE(left), SIZE(right))  :: product
     278              : 
     279              :       INTEGER                                            :: i, j
     280              : 
     281           24 :       DO j = 1, SIZE(right)
     282           78 :          DO i = 1, SIZE(left)
     283           72 :             PRODUCT(i, j) = left(i)*right(j)
     284              :          END DO
     285              :       END DO
     286              : 
     287            6 :    END FUNCTION outer_product
     288              : 
     289              : ! **************************************************************************************************
     290              : !> \brief Abort when two matrices differ beyond the requested tolerance.
     291              : !> \param actual ...
     292              : !> \param reference ...
     293              : !> \param tolerance ...
     294              : !> \param label ...
     295              : ! **************************************************************************************************
     296            8 :    SUBROUTINE assert_close(actual, reference, tolerance, label)
     297              : 
     298              :       REAL(KIND=dp), DIMENSION(:, :), INTENT(IN)         :: actual, reference
     299              :       REAL(KIND=dp), INTENT(IN)                          :: tolerance
     300              :       CHARACTER(LEN=*), INTENT(IN)                       :: label
     301              : 
     302              :       INTEGER                                            :: io_unit
     303              : 
     304          152 :       IF (MAXVAL(ABS(actual - reference)) > tolerance) THEN
     305            0 :          io_unit = error_unit
     306            0 :          WRITE (io_unit, '(A)') TRIM(label)
     307            0 :          ERROR STOP "Matrix comparison failed"
     308              :       END IF
     309              : 
     310            8 :    END SUBROUTINE assert_close
     311              : 
     312              : END PROGRAM ot_covariant_preconditioner_unittest
        

Generated by: LCOV version 2.0-1