numdiff.f90 Source File


Source Code

! This file is part of dftd4.
! SPDX-Identifier: LGPL-3.0-or-later
!
! dftd4 is free software: you can redistribute it and/or modify it under
! the terms of the Lesser GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! dftd4 is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! Lesser GNU General Public License for more details.
!
! You should have received a copy of the Lesser GNU General Public License
! along with dftd4.  If not, see <https://www.gnu.org/licenses/>.

!> Numerical differentiation of DFT-D4 model
module dftd4_numdiff
   use, intrinsic :: ieee_arithmetic, only : ieee_value, ieee_quiet_nan
   use dftd4_cutoff, only : realspace_cutoff
   use dftd4_damping, only : damping_param
   use dftd4_damping_rational, only : rational_damping_param
   use dftd4_disp, only : get_dispersion, get_dispersion2, get_dispersion3_hessian
   use dftd4_model, only : dispersion_model, d4_model, d4s_model
   use dftd4_partition, only : work_partition
   use mctc_env, only : error_type, wp
   use mctc_io, only : structure_type
   implicit none
   private

   public :: get_dispersion_hessian


contains


!> Evaluate Hessian matrix using an analytical ATM contribution when available
subroutine get_dispersion_hessian(mol, disp, param, cutoff, hessian, partition, error)
   !DEC$ ATTRIBUTES DLLEXPORT :: get_dispersion_hessian

   !> Molecular structure data
   class(structure_type), intent(in) :: mol

   !> Dispersion model
   class(dispersion_model), intent(in) :: disp

   !> Damping parameters
   class(damping_param), intent(in) :: param

   !> Realspace cutoffs
   type(realspace_cutoff), intent(in) :: cutoff

   !> Dispersion Hessian
   real(wp), intent(out) :: hessian(:, :, :, :)

   !> Work partition of the interaction loops, defaults to the complete work
   type(work_partition), intent(in), optional :: partition

   !> Error on failure; the entire Hessian is set to NaN on failure.
   type(error_type), allocatable, intent(out), optional :: error

   integer :: iat, ix, jat, jx, ii, jj, ndim
   logical :: analytical_atm
   real(wp), parameter :: step = 1.0e-4_wp
   type(structure_type) :: displ
   type(error_type), allocatable :: local_error, first_error
   real(wp) :: el, er
   real(wp), allocatable :: gl(:, :), gr(:, :), sl(:, :), sr(:, :), hessian3(:, :)

   analytical_atm = .false.
   select type(param)
   class is (rational_damping_param)
      select type(disp)
      type is (d4_model)
         analytical_atm = .true.
      type is (d4s_model)
         analytical_atm = .true.
      end select
   end select

   hessian(:, :, :, :) = 0.0_wp
   !$omp parallel default(none) &
   !$omp private(iat, ix, displ, er, el, gr, gl, sr, sl, local_error) &
   !$omp shared(mol, disp, param, cutoff, hessian, partition, analytical_atm, first_error)
   displ = mol
   allocate(gl(3, mol%nat), gr(3, mol%nat), sl(3, 3), sr(3, 3))
   !$omp do schedule(dynamic) collapse(2)
   do iat = 1, mol%nat
      do ix = 1, 3
         ! A failed worker skips its remaining displacements, retaining its error.
         if (allocated(local_error)) cycle
         displ%xyz(ix, iat) = mol%xyz(ix, iat) + step
         if (analytical_atm) then
            call get_dispersion2(displ, disp, param, cutoff, el, gl, sl, partition, local_error)
         else
            call get_dispersion(displ, disp, param, cutoff, el, gl, sl, partition, local_error)
         end if
         if (allocated(local_error)) then
            displ%xyz(ix, iat) = mol%xyz(ix, iat)
            cycle
         end if

         displ%xyz(ix, iat) = mol%xyz(ix, iat) - step
         if (analytical_atm) then
            call get_dispersion2(displ, disp, param, cutoff, er, gr, sr, partition, local_error)
         else
            call get_dispersion(displ, disp, param, cutoff, er, gr, sr, partition, local_error)
         end if

         displ%xyz(ix, iat) = mol%xyz(ix, iat)
         if (allocated(local_error)) cycle
         hessian(:, :, ix, iat) = (gl - gr) / (2 * step)
      end do
   end do
   !$omp end do
   if (allocated(local_error)) then
      !$omp critical(dftd4_hessian_error)
      if (.not. allocated(first_error)) first_error = local_error
      !$omp end critical(dftd4_hessian_error)
   end if
   !$omp end parallel

   if (allocated(first_error)) then
      hessian = ieee_value(0.0_wp, ieee_quiet_nan)
      if (present(error)) call move_alloc(first_error, error)
      return
   end if

   if (analytical_atm) then
      ndim = 3*mol%nat
      allocate(hessian3(ndim, ndim))
      call get_dispersion3_hessian(local_error, mol, disp, param, cutoff, hessian3, partition)
      if (allocated(local_error)) then
         hessian = ieee_value(0.0_wp, ieee_quiet_nan)
         if (present(error)) call move_alloc(local_error, error)
         return
      end if

      !$omp parallel do collapse(4) schedule(static) default(none) &
      !$omp shared(mol, hessian, hessian3) private(jat, jx, iat, ix, ii, jj)
      do jat = 1, mol%nat
         do jx = 1, 3
            do iat = 1, mol%nat
               do ix = 1, 3
                  jj = 3*(jat - 1) + jx
                  ii = 3*(iat - 1) + ix
                  hessian(ix, iat, jx, jat) = hessian(ix, iat, jx, jat) + hessian3(ii, jj)
               end do
            end do
         end do
      end do
      !$omp end parallel do
   end if

end subroutine get_dispersion_hessian

end module dftd4_numdiff