LCOV - code coverage report
Current view: top level - src - kpoint_smearing_unittest.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:b310db4) Lines: 92.7 % 82 76
Test Date: 2026-10-23 01:10:43 Functions: 100.0 % 4 4

            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            2 : PROGRAM kpoint_smearing_unittest
       9            2 :    USE bibliography,                    ONLY: add_all_references
      10              :    USE input_constants,                 ONLY: smear_fermi_dirac,&
      11              :                                               smear_gaussian,&
      12              :                                               smear_mp,&
      13              :                                               smear_mv
      14              :    USE kinds,                           ONLY: dp
      15              :    USE kpoint_methods,                  ONLY: kpoint_smearing_edge_status
      16              :    USE qs_ot,                           ONLY: qs_ot_fixed_n_energy_hessian
      17              :    USE reference_manager,               ONLY: remove_all_references
      18              :    USE scf_control_types,               ONLY: smear_type
      19              :    USE smearing_utils,                  ONLY: Smearkp,&
      20              :                                               smearing_response_weight,&
      21              :                                               smearing_separate_spin,&
      22              :                                               smearing_spin_targets
      23              : 
      24              :    IMPLICIT NONE
      25              : 
      26              :    LOGICAL                                            :: first_fractional, has_weight, last_occupied
      27              :    LOGICAL, DIMENSION(2)                              :: last_occupied_spin
      28              :    REAL(KIND=dp), DIMENSION(2)                        :: weight
      29              :    REAL(KIND=dp), DIMENSION(3, 2, 2)                  :: occupation
      30              :    TYPE(smear_type)                                   :: smear
      31              : 
      32            2 :    CALL add_all_references()
      33            2 :    smear%do_smear = .TRUE.
      34            2 :    smear%eps_fermi_dirac = 1.0E-5_dp
      35            2 :    occupation = 0.0_dp
      36           14 :    occupation(1, :, :) = 1.0_dp
      37            2 :    weight = [1.0_dp, 0.0_dp]
      38              : 
      39            2 :    occupation(1, 2, 1) = 0.5_dp
      40            2 :    occupation(3, 2, 2) = 0.5_dp
      41              :    CALL kpoint_smearing_edge_status(occupation, weight, smear, 2, has_weight, &
      42            2 :                                     first_fractional, last_occupied, last_occupied_spin)
      43            2 :    IF (.NOT. has_weight) ERROR STOP "Positive K-point weight was not detected"
      44            2 :    IF (first_fractional) ERROR STOP "Zero-weight first-band occupation was included"
      45            2 :    IF (last_occupied) ERROR STOP "Zero-weight last-band occupation was included"
      46            6 :    IF (ANY(last_occupied_spin)) ERROR STOP "Zero-weight spin edge was included"
      47              : 
      48            2 :    weight(2) = 0.25_dp
      49              :    CALL kpoint_smearing_edge_status(occupation, weight, smear, 2, has_weight, &
      50            2 :                                     first_fractional, last_occupied, last_occupied_spin)
      51            2 :    IF (.NOT. first_fractional) ERROR STOP "Fractional first band was not detected"
      52            2 :    IF (.NOT. last_occupied) ERROR STOP "Occupied last band was not detected"
      53            2 :    IF (last_occupied_spin(1)) ERROR STOP "Wrong occupied spin edge was reported"
      54            2 :    IF (.NOT. last_occupied_spin(2)) ERROR STOP "Occupied spin edge was not reported"
      55              : 
      56            2 :    occupation = 0.0_dp
      57            6 :    occupation(1, :, 1) = 2.0_dp
      58              :    CALL kpoint_smearing_edge_status(occupation(:, :, 1:1), weight, smear, 1, has_weight, &
      59            2 :                                     first_fractional, last_occupied)
      60            2 :    IF (first_fractional) ERROR STOP "Restricted full occupation was classified as fractional"
      61              : 
      62            2 :    weight = 0.0_dp
      63              :    CALL kpoint_smearing_edge_status(occupation, weight, smear, 2, has_weight, &
      64            2 :                                     first_fractional, last_occupied, last_occupied_spin)
      65            2 :    IF (has_weight .OR. first_fractional .OR. last_occupied) THEN
      66            0 :       ERROR STOP "Zero-weight K-point set produced an edge status"
      67              :    END IF
      68              : 
      69            2 :    smear%do_smear = .FALSE.
      70            6 :    weight = 1.0_dp
      71              :    CALL kpoint_smearing_edge_status(occupation, weight, smear, 2, has_weight, &
      72            2 :                                     first_fractional, last_occupied, last_occupied_spin)
      73            2 :    IF (has_weight .OR. first_fractional .OR. last_occupied) THEN
      74            0 :       ERROR STOP "Disabled smearing produced an edge status"
      75              :    END IF
      76              : 
      77            2 :    CALL test_fixed_n_smearing_response()
      78            2 :    CALL test_fixed_spin_populations()
      79            2 :    CALL remove_all_references()
      80              : 
      81              : CONTAINS
      82              : 
      83              : ! **************************************************************************************************
      84              : !> \brief Check fractional, zero, integer-preserving, and unconstrained spin targets.
      85              : ! **************************************************************************************************
      86            2 :    SUBROUTINE test_fixed_spin_populations()
      87              :       REAL(KIND=dp), DIMENSION(2)                        :: targets
      88              : 
      89            2 :       IF (smearing_separate_spin(-100.0_dp)) ERROR STOP "Unconstrained spin was fixed"
      90            2 :       IF (.NOT. smearing_separate_spin(-1.0_dp)) ERROR STOP "Integer spin populations were released"
      91            2 :       IF (.NOT. smearing_separate_spin(0.0_dp)) ERROR STOP "Zero magnetization was not constrained"
      92            2 :       IF (.NOT. smearing_separate_spin(0.5_dp)) ERROR STOP "Fractional magnetization was not constrained"
      93            2 :       targets(:) = smearing_spin_targets([3, 1], -1.0_dp)
      94            6 :       IF (MAXVAL(ABS(targets - [3.0_dp, 1.0_dp])) > 1.0E-14_dp) THEN
      95            0 :          ERROR STOP "Integer spin targets changed"
      96              :       END IF
      97            2 :       targets(:) = smearing_spin_targets([3, 1], 0.0_dp)
      98            6 :       IF (MAXVAL(ABS(targets - 2.0_dp)) > 1.0E-14_dp) ERROR STOP "Zero-moment targets are wrong"
      99            2 :       targets(:) = smearing_spin_targets([3, 1], 0.5_dp)
     100            6 :       IF (MAXVAL(ABS(targets - [2.25_dp, 1.75_dp])) > 1.0E-14_dp) THEN
     101            0 :          ERROR STOP "Fractional spin targets were rounded"
     102              :       END IF
     103            2 :    END SUBROUTINE test_fixed_spin_populations
     104              : 
     105              : ! **************************************************************************************************
     106              : !> \brief Check the fixed-N occupation response for every supported K-point smearing method.
     107              : ! **************************************************************************************************
     108            2 :    SUBROUTINE test_fixed_n_smearing_response()
     109              :       INTEGER, PARAMETER                                 :: nkpoint = 2, nstate = 5
     110              :       REAL(KIND=dp), PARAMETER                           :: fd_step = 1.0E-6_dp, maxocc = 2.0_dp, &
     111              :                                                             sigma = 0.15_dp, TARGET = 4.3_dp
     112              : 
     113              :       INTEGER                                            :: ikpoint, imethod
     114              :       INTEGER, DIMENSION(4)                              :: methods
     115              :       REAL(KIND=dp)                                      :: error, kTS, mu, response_sum
     116              :       REAL(KIND=dp), DIMENSION(nkpoint)                  :: kpoint_weight
     117              :       REAL(KIND=dp), DIMENSION(nstate*nkpoint)           :: fd_response, flat_direction, &
     118              :                                                             predicted_response
     119              :       REAL(KIND=dp), &
     120              :          DIMENSION(nstate*nkpoint, nstate*nkpoint)       :: hessian
     121              :       REAL(KIND=dp), DIMENSION(nstate, nkpoint) :: direction, eigenvalue, eigenvalue_minus, &
     122              :          eigenvalue_plus, occupation, occupation_minus, occupation_plus, response
     123              : 
     124            2 :       methods(:) = [smear_fermi_dirac, smear_gaussian, smear_mp, smear_mv]
     125            2 :       kpoint_weight(:) = [0.4_dp, 0.6_dp]
     126           12 :       eigenvalue(:, 1) = [-0.38_dp, -0.14_dp, 0.02_dp, 0.23_dp, 0.52_dp]
     127           12 :       eigenvalue(:, 2) = [-0.31_dp, -0.08_dp, 0.07_dp, 0.28_dp, 0.61_dp]
     128           12 :       direction(:, 1) = [0.13_dp, -0.07_dp, 0.11_dp, -0.04_dp, 0.09_dp]
     129           12 :       direction(:, 2) = [-0.08_dp, 0.05_dp, -0.12_dp, 0.06_dp, -0.03_dp]
     130            2 :       flat_direction(:) = RESHAPE(direction, [nstate*nkpoint])
     131              : 
     132           10 :       DO imethod = 1, SIZE(methods)
     133              :          CALL Smearkp(occupation, mu, kTS, eigenvalue, TARGET, kpoint_weight, sigma, maxocc, &
     134            8 :                       methods(imethod))
     135           24 :          DO ikpoint = 1, nkpoint
     136              :             CALL smearing_response_weight( &
     137              :                response(:, ikpoint), occupation(:, ikpoint), eigenvalue(:, ikpoint), mu, sigma, &
     138           16 :                maxocc, nstate, methods(imethod))
     139          104 :             response(:, ikpoint) = kpoint_weight(ikpoint)*response(:, ikpoint)
     140              :          END DO
     141          104 :          response_sum = SUM(response)
     142            8 :          IF (ABS(response_sum) <= EPSILON(response_sum)) THEN
     143            0 :             ERROR STOP "Singular fixed-N smearing response in unit test"
     144              :          END IF
     145           56 :          IF ((methods(imethod) == smear_mp .OR. methods(imethod) == smear_mv) .AND. &
     146              :              .NOT. ANY(response < 0.0_dp)) THEN
     147            0 :             ERROR STOP "Signed smearing-response branch was not exercised"
     148              :          END IF
     149              :          CALL qs_ot_fixed_n_energy_hessian( &
     150            8 :             RESHAPE(response, [nstate*nkpoint]), response_sum, hessian)
     151              : 
     152          104 :          eigenvalue_plus(:, :) = eigenvalue + fd_step*direction
     153          104 :          eigenvalue_minus(:, :) = eigenvalue - fd_step*direction
     154              :          CALL Smearkp(occupation_plus, mu, kTS, eigenvalue_plus, TARGET, kpoint_weight, sigma, &
     155            8 :                       maxocc, methods(imethod))
     156              :          CALL Smearkp(occupation_minus, mu, kTS, eigenvalue_minus, TARGET, kpoint_weight, sigma, &
     157            8 :                       maxocc, methods(imethod))
     158           24 :          DO ikpoint = 1, nkpoint
     159              :             fd_response((ikpoint - 1)*nstate + 1:ikpoint*nstate) = &
     160              :                kpoint_weight(ikpoint)*(occupation_plus(:, ikpoint) - &
     161          104 :                                        occupation_minus(:, ikpoint))/(2.0_dp*fd_step)
     162              :          END DO
     163          976 :          predicted_response(:) = -MATMUL(hessian, flat_direction)
     164           88 :          error = MAXVAL(ABS(fd_response - predicted_response))
     165           88 :          error = MAX(error, ABS(SUM(fd_response)))
     166          976 :          error = MAX(error, MAXVAL(ABS(MATMUL(hessian, SPREAD(1.0_dp, 1, nstate*nkpoint)))))
     167           10 :          IF (error > 2.0E-6_dp) ERROR STOP "Fixed-N smearing response finite difference failed"
     168              :       END DO
     169              : 
     170            2 :    END SUBROUTINE test_fixed_n_smearing_response
     171              : 
     172              : END PROGRAM kpoint_smearing_unittest
        

Generated by: LCOV version 2.0-1