LCOV - code coverage report
Current view: top level - src - surface_dipole.F (source / functions) Coverage Total Hit
Test: CP2K Regtests (git:24d69ee) Lines: 81.2 % 133 108
Test Date: 2026-09-03 07:32:15 Functions: 100.0 % 1 1

            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              : MODULE surface_dipole
      10              : 
      11              :    USE cell_types,                      ONLY: cell_type
      12              :    USE cp_control_types,                ONLY: dft_control_type
      13              :    USE kahan_sum,                       ONLY: accurate_sum
      14              :    USE kinds,                           ONLY: dp
      15              :    USE mathconstants,                   ONLY: pi
      16              :    USE physcon,                         ONLY: bohr,&
      17              :                                               evolt
      18              :    USE pw_env_types,                    ONLY: pw_env_get,&
      19              :                                               pw_env_type
      20              :    USE pw_grid_types,                   ONLY: PW_MODE_LOCAL
      21              :    USE pw_methods,                      ONLY: pw_axpy,&
      22              :                                               pw_integral_ab,&
      23              :                                               pw_scale,&
      24              :                                               pw_transfer,&
      25              :                                               pw_zero
      26              :    USE pw_pool_types,                   ONLY: pw_pool_p_type,&
      27              :                                               pw_pool_type
      28              :    USE pw_types,                        ONLY: pw_c1d_gs_type,&
      29              :                                               pw_r3d_rs_type
      30              :    USE qs_energy_types,                 ONLY: qs_energy_type
      31              :    USE qs_environment_types,            ONLY: get_qs_env,&
      32              :                                               qs_environment_type
      33              :    USE qs_rho_types,                    ONLY: qs_rho_get,&
      34              :                                               qs_rho_type
      35              :    USE qs_subsys_types,                 ONLY: qs_subsys_type
      36              : #include "./base/base_uses.f90"
      37              : 
      38              :    IMPLICIT NONE
      39              : 
      40              :    PRIVATE
      41              : 
      42              :    CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'surface_dipole'
      43              : 
      44              :    PUBLIC :: calc_dipsurf_potential
      45              : 
      46              : CONTAINS
      47              : 
      48              : ! **************************************************************************************************
      49              : !> \brief compute the surface dipole and the correction to the hartree potential
      50              : !> \param qs_env the qs environment
      51              : !> \param energy ...
      52              : !> \par History
      53              : !>      01.2014 created [MI]
      54              : !> \author MI
      55              : !> \author Soumya Ghosh added SURF_DIP_POS 19.11.2018
      56              : ! **************************************************************************************************
      57              : 
      58          110 :    SUBROUTINE calc_dipsurf_potential(qs_env, energy)
      59              : 
      60              :       TYPE(qs_environment_type), POINTER                 :: qs_env
      61              :       TYPE(qs_energy_type), POINTER                      :: energy
      62              : 
      63              :       CHARACTER(len=*), PARAMETER :: routineN = 'calc_dipsurf_potential'
      64              : 
      65              :       INTEGER                                            :: handle, i, i_above, i_below, &
      66              :                                                             idir_surfdip, ilayer_min, ilow, irho, &
      67              :                                                             ispin, isurf, iup, jsurf, width
      68              :       INTEGER, DIMENSION(3)                              :: ngrid
      69          110 :       INTEGER, DIMENSION(:, :), POINTER                  :: bo
      70              :       REAL(dp) :: cutoff, dh(3, 3), dip_fac, dip_hh, dsurf, height_min, hh, pos_surf_dip, &
      71              :          rhoav_min, surfarea, vac_above, vac_below, vdip, vdip_fac
      72              :       REAL(dp), ALLOCATABLE, DIMENSION(:)                :: rhoavsurf
      73              :       TYPE(cell_type), POINTER                           :: cell
      74              :       TYPE(dft_control_type), POINTER                    :: dft_control
      75              :       TYPE(pw_c1d_gs_type), POINTER                      :: rho0_s_gs, rho_core, rhoz_cneo_s_gs
      76              :       TYPE(pw_env_type), POINTER                         :: pw_env
      77          110 :       TYPE(pw_pool_p_type), DIMENSION(:), POINTER        :: pw_pools
      78              :       TYPE(pw_pool_type), POINTER                        :: auxbas_pw_pool
      79              :       TYPE(pw_r3d_rs_type)                               :: vdip_r, wf_r
      80          110 :       TYPE(pw_r3d_rs_type), DIMENSION(:), POINTER        :: rho_r
      81              :       TYPE(pw_r3d_rs_type), POINTER                      :: v_hartree_rspace
      82              :       TYPE(qs_rho_type), POINTER                         :: rho
      83              :       TYPE(qs_subsys_type), POINTER                      :: subsys
      84              : 
      85          110 :       CALL timeset(routineN, handle)
      86          110 :       NULLIFY (cell, dft_control, rho, pw_env, auxbas_pw_pool, &
      87          110 :                pw_pools, subsys, v_hartree_rspace, rho_r, rhoz_cneo_s_gs)
      88              : 
      89              :       CALL get_qs_env(qs_env, &
      90              :                       dft_control=dft_control, &
      91              :                       rho=rho, &
      92              :                       rho_core=rho_core, &
      93              :                       rho0_s_gs=rho0_s_gs, &
      94              :                       rhoz_cneo_s_gs=rhoz_cneo_s_gs, &
      95              :                       cell=cell, &
      96              :                       pw_env=pw_env, &
      97              :                       subsys=subsys, &
      98          110 :                       v_hartree_rspace=v_hartree_rspace)
      99              : 
     100              :       CALL pw_env_get(pw_env, auxbas_pw_pool=auxbas_pw_pool, &
     101          110 :                       pw_pools=pw_pools)
     102          110 :       CALL auxbas_pw_pool%create_pw(wf_r)
     103          110 :       CALL auxbas_pw_pool%create_pw(vdip_r)
     104              : 
     105          110 :       IF (dft_control%qs_control%gapw) THEN
     106            0 :          IF (dft_control%qs_control%gapw_control%nopaw_as_gpw) THEN
     107            0 :             CALL pw_axpy(rho_core, rho0_s_gs)
     108            0 :             IF (ASSOCIATED(rhoz_cneo_s_gs)) THEN
     109            0 :                CALL pw_axpy(rhoz_cneo_s_gs, rho0_s_gs)
     110              :             END IF
     111            0 :             CALL pw_transfer(rho0_s_gs, wf_r)
     112            0 :             CALL pw_axpy(rho_core, rho0_s_gs, -1.0_dp)
     113            0 :             IF (ASSOCIATED(rhoz_cneo_s_gs)) THEN
     114            0 :                CALL pw_axpy(rhoz_cneo_s_gs, rho0_s_gs, -1.0_dp)
     115              :             END IF
     116              :          ELSE
     117            0 :             IF (ASSOCIATED(rhoz_cneo_s_gs)) THEN
     118            0 :                CALL pw_axpy(rhoz_cneo_s_gs, rho0_s_gs)
     119              :             END IF
     120            0 :             CALL pw_transfer(rho0_s_gs, wf_r)
     121            0 :             IF (ASSOCIATED(rhoz_cneo_s_gs)) THEN
     122            0 :                CALL pw_axpy(rhoz_cneo_s_gs, rho0_s_gs, -1.0_dp)
     123              :             END IF
     124              :          END IF
     125              :       ELSE
     126          110 :          CALL pw_transfer(rho_core, wf_r)
     127              :       END IF
     128          110 :       CALL qs_rho_get(rho, rho_r=rho_r)
     129          240 :       DO ispin = 1, dft_control%nspins
     130          240 :          CALL pw_axpy(rho_r(ispin), wf_r)
     131              :       END DO
     132              : 
     133          440 :       ngrid(1:3) = wf_r%pw_grid%npts(1:3)
     134          110 :       idir_surfdip = dft_control%dir_surf_dip
     135              : 
     136          110 :       width = 4
     137              : 
     138          440 :       DO i = 1, 3
     139          440 :          IF (i /= idir_surfdip) THEN
     140          220 :             IF (ABS(wf_r%pw_grid%dh(idir_surfdip, i)) > 1.E-7_dp) THEN
     141              :                ! stop surface dipole defined only for slab perpendigular to one of the Cartesian axis
     142              :                CALL cp_abort(__LOCATION__, &
     143            0 :                              "Dipole correction only for surface perpendicular to one Cartesian axis")
     144              : !  not properly general, we assume that vectors A, B, and C are along x y and z respectively,
     145              : !  in the ortorhombic cell, but in principle it does not need to be this way, importan
     146              : !  is that the cell angles are 90 degrees.
     147              :             END IF
     148              :          END IF
     149              :       END DO
     150              : 
     151          110 :       ilow = wf_r%pw_grid%bounds(1, idir_surfdip)
     152          110 :       iup = wf_r%pw_grid%bounds(2, idir_surfdip)
     153              : 
     154          330 :       ALLOCATE (rhoavsurf(ilow:iup))
     155          110 :       rhoavsurf = 0.0_dp
     156              : 
     157          110 :       bo => wf_r%pw_grid%bounds_local
     158         1430 :       dh = wf_r%pw_grid%dh
     159              : 
     160          110 :       CALL pw_scale(wf_r, wf_r%pw_grid%vol)
     161          110 :       IF (idir_surfdip == 3) THEN
     162           56 :          isurf = 1
     163           56 :          jsurf = 2
     164              : 
     165        13784 :          DO i = bo(1, 3), bo(2, 3)
     166        13784 :             rhoavsurf(i) = accurate_sum(wf_r%array(bo(1, 1):bo(2, 1), bo(1, 2):bo(2, 2), i))
     167              :          END DO
     168              : 
     169           54 :       ELSE IF (idir_surfdip == 2) THEN
     170            0 :          isurf = 3
     171            0 :          jsurf = 1
     172              : 
     173            0 :          DO i = bo(1, 2), bo(2, 2)
     174            0 :             rhoavsurf(i) = accurate_sum(wf_r%array(bo(1, 1):bo(2, 1), i, bo(1, 3):bo(2, 3)))
     175              :          END DO
     176              :       ELSE
     177           54 :          isurf = 2
     178           54 :          jsurf = 3
     179              : 
     180         6854 :          DO i = bo(1, 1), bo(2, 1)
     181         6854 :             rhoavsurf(i) = accurate_sum(wf_r%array(i, bo(1, 2):bo(2, 2), bo(1, 3):bo(2, 3)))
     182              :          END DO
     183              :       END IF
     184          110 :       CALL pw_scale(wf_r, 1.0_dp/wf_r%pw_grid%vol)
     185        27438 :       rhoavsurf = rhoavsurf/wf_r%pw_grid%vol
     186              : 
     187              :       surfarea = cell%hmat(isurf, isurf)*cell%hmat(jsurf, jsurf) - &
     188          110 :                  cell%hmat(isurf, jsurf)*cell%hmat(jsurf, isurf)
     189          110 :       dsurf = surfarea/REAL(ngrid(isurf)*ngrid(jsurf), dp)
     190              : 
     191          110 :       IF (wf_r%pw_grid%para%mode /= PW_MODE_LOCAL) THEN
     192          110 :          CALL wf_r%pw_grid%para%group%sum(rhoavsurf)
     193              :       END IF
     194        27438 :       rhoavsurf(ilow:iup) = dsurf*rhoavsurf(ilow:iup)
     195              : 
     196              :       ! locate where the vacuum is, and set the reference point for the calculation of the dipole
     197        27438 :       rhoavsurf(ilow:iup) = rhoavsurf(ilow:iup)/surfarea
     198              :       ! Note: rhosurf has the same dimension as rho
     199          110 :       IF (dft_control%pos_dir_surf_dip < 0.0_dp) THEN
     200         3200 :          ilayer_min = ilow - 1 + MINLOC(ABS(rhoavsurf(ilow:iup)), 1)
     201              :       ELSE
     202           78 :          pos_surf_dip = dft_control%pos_dir_surf_dip*bohr
     203           78 :          ilayer_min = ilow - 1 + NINT(pos_surf_dip/dh(idir_surfdip, idir_surfdip)) + 1
     204              :       END IF
     205          110 :       rhoav_min = ABS(rhoavsurf(ilayer_min))
     206          110 :       IF (rhoav_min >= 1.E-5_dp) THEN
     207            0 :          CPABORT(" Dipole correction needs more vacuum space above the surface ")
     208              :       END IF
     209              : 
     210          110 :       height_min = REAL((ilayer_min - ilow), dp)*dh(idir_surfdip, idir_surfdip)
     211              : 
     212              : !   surface dipole form average rhoavsurf
     213              : !   \sum_i NjdjNkdkdi rhoav_i (i-imin)di
     214          110 :       dip_hh = 0.0_dp
     215          110 :       dip_fac = wf_r%pw_grid%vol*dh(idir_surfdip, idir_surfdip)/REAL(ngrid(idir_surfdip), dp)
     216              : 
     217        27438 :       DO i = ilayer_min + 1, ilayer_min + ngrid(idir_surfdip)
     218        27328 :          hh = REAL((i - ilayer_min), dp)
     219        27328 :          IF (i > iup) THEN
     220        19702 :             irho = i - ngrid(idir_surfdip)
     221              :          ELSE
     222              :             irho = i
     223              :          END IF
     224              : ! introduce a cutoff function to smoothen the edges
     225        27328 :          IF (ABS(irho - ilayer_min) > width) THEN
     226              :             cutoff = 1.0_dp
     227              :          ELSE
     228          990 :             cutoff = ABS(SIN(0.5_dp*pi*REAL(ABS(irho - ilayer_min), dp)/REAL(width, dp)))
     229              :          END IF
     230        27438 :          dip_hh = dip_hh + rhoavsurf(irho)*hh*dip_fac*cutoff
     231              :       END DO
     232              : 
     233          110 :       DEALLOCATE (rhoavsurf)
     234              : ! for printing purposes [SGh]
     235          110 :       qs_env%surface_dipole_moment = dip_hh/bohr
     236          110 :       qs_env%surface_dipole_ref_pos = height_min/bohr
     237              : 
     238              : !    Calculation of the dipole potential as a function of the perpendicular coordinate
     239          110 :       CALL pw_zero(vdip_r)
     240          110 :       vdip_fac = dip_hh*4.0_dp*pi
     241              : 
     242        27438 :       DO i = ilayer_min + 1, ilayer_min + ngrid(idir_surfdip)
     243        27328 :          hh = REAL((i - ilayer_min), dp)*dh(idir_surfdip, idir_surfdip)
     244              :          vdip = vdip_fac*(-0.5_dp + (hh/cell%hmat(idir_surfdip, idir_surfdip)))* &
     245        27328 :                 v_hartree_rspace%pw_grid%dvol/surfarea
     246        27328 :          IF (i > iup) THEN
     247        19702 :             irho = i - ngrid(idir_surfdip)
     248              :          ELSE
     249              :             irho = i
     250              :          END IF
     251              : ! introduce a cutoff function to smoothen the edges
     252        27328 :          IF (ABS(irho - ilayer_min) > width) THEN
     253              :             cutoff = 1.0_dp
     254              :          ELSE
     255          990 :             cutoff = ABS(SIN(0.5_dp*pi*REAL(ABS(irho - ilayer_min), dp)/REAL(width, dp)))
     256              :          END IF
     257        27328 :          vdip = vdip*cutoff
     258              : 
     259        27438 :          IF (idir_surfdip == 3) THEN
     260              :             vdip_r%array(bo(1, 1):bo(2, 1), bo(1, 2):bo(2, 2), irho) = &
     261     17343264 :                vdip_r%array(bo(1, 1):bo(2, 1), bo(1, 2):bo(2, 2), irho) + vdip
     262        13600 :          ELSE IF (idir_surfdip == 2) THEN
     263            0 :             IF (irho >= bo(1, 2) .AND. irho <= bo(2, 2)) THEN
     264              :                vdip_r%array(bo(1, 1):bo(2, 1), irho, bo(1, 3):bo(2, 3)) = &
     265            0 :                   vdip_r%array(bo(1, 1):bo(2, 1), irho, bo(1, 3):bo(2, 3)) + vdip
     266              :             END IF
     267              :          ELSE
     268        13600 :             IF (irho >= bo(1, 1) .AND. irho <= bo(2, 1)) THEN
     269              :                vdip_r%array(irho, bo(1, 2):bo(2, 2), bo(1, 3):bo(2, 3)) = &
     270     26378320 :                   vdip_r%array(irho, bo(1, 2):bo(2, 2), bo(1, 3):bo(2, 3)) + vdip
     271              :             END IF
     272              :          END IF
     273              : 
     274              :       END DO
     275              : 
     276              : !    Dipole correction contribution to the energy
     277          110 :       energy%surf_dipole = 0.5_dp*pw_integral_ab(vdip_r, wf_r, just_sum=.TRUE.)
     278              : 
     279              : !    Add the dipole potential to the hartree potential on the realspace grid
     280          110 :       CALL pw_axpy(vdip_r, v_hartree_rspace)
     281              : 
     282              : !    Vacuum level (plane-averaged, corrected Hartree potential) immediately below and above the
     283              : !    dipole correction reference plane. Note: v_hartree_rspace does not carry the GAPW one-center
     284              : !    (hard/soft) corrections, but those are localized on the atoms and vanish at these sampling
     285              : !    points, which by construction lie in the vacuum.
     286          110 :       i_below = ilayer_min - 1
     287          110 :       IF (i_below < ilow) i_below = iup
     288          110 :       i_above = ilayer_min + 1
     289          110 :       IF (i_above > iup) i_above = ilow
     290              : 
     291          110 :       vac_below = 0.0_dp
     292          110 :       vac_above = 0.0_dp
     293          110 :       IF (idir_surfdip == 3) THEN
     294           56 :          IF (i_below >= bo(1, 3) .AND. i_below <= bo(2, 3)) THEN
     295           56 :             vac_below = accurate_sum(v_hartree_rspace%array(bo(1, 1):bo(2, 1), bo(1, 2):bo(2, 2), i_below))
     296              :          END IF
     297           56 :          IF (i_above >= bo(1, 3) .AND. i_above <= bo(2, 3)) THEN
     298           56 :             vac_above = accurate_sum(v_hartree_rspace%array(bo(1, 1):bo(2, 1), bo(1, 2):bo(2, 2), i_above))
     299              :          END IF
     300           54 :       ELSE IF (idir_surfdip == 2) THEN
     301            0 :          IF (i_below >= bo(1, 2) .AND. i_below <= bo(2, 2)) THEN
     302            0 :             vac_below = accurate_sum(v_hartree_rspace%array(bo(1, 1):bo(2, 1), i_below, bo(1, 3):bo(2, 3)))
     303              :          END IF
     304            0 :          IF (i_above >= bo(1, 2) .AND. i_above <= bo(2, 2)) THEN
     305            0 :             vac_above = accurate_sum(v_hartree_rspace%array(bo(1, 1):bo(2, 1), i_above, bo(1, 3):bo(2, 3)))
     306              :          END IF
     307              :       ELSE
     308           54 :          IF (i_below >= bo(1, 1) .AND. i_below <= bo(2, 1)) THEN
     309           27 :             vac_below = accurate_sum(v_hartree_rspace%array(i_below, bo(1, 2):bo(2, 2), bo(1, 3):bo(2, 3)))
     310              :          END IF
     311           54 :          IF (i_above >= bo(1, 1) .AND. i_above <= bo(2, 1)) THEN
     312           27 :             vac_above = accurate_sum(v_hartree_rspace%array(i_above, bo(1, 2):bo(2, 2), bo(1, 3):bo(2, 3)))
     313              :          END IF
     314              :       END IF
     315              : 
     316          110 :       IF (wf_r%pw_grid%para%mode /= PW_MODE_LOCAL) THEN
     317          110 :          CALL wf_r%pw_grid%para%group%sum(vac_below)
     318          110 :          CALL wf_r%pw_grid%para%group%sum(vac_above)
     319              :       END IF
     320              : 
     321          110 :       qs_env%vacuum_level_below = vac_below/REAL(ngrid(isurf)*ngrid(jsurf), dp)*evolt
     322          110 :       qs_env%vacuum_level_above = vac_above/REAL(ngrid(isurf)*ngrid(jsurf), dp)*evolt
     323              : 
     324          110 :       CALL auxbas_pw_pool%give_back_pw(wf_r)
     325          110 :       CALL auxbas_pw_pool%give_back_pw(vdip_r)
     326              : 
     327          110 :       CALL timestop(handle)
     328              : 
     329          110 :    END SUBROUTINE calc_dipsurf_potential
     330              : 
     331              : END MODULE surface_dipole
        

Generated by: LCOV version 2.0-1