get_atm_dispersion_hessian Subroutine

public subroutine get_atm_dispersion_hessian(mol, trans, cutoff, width, s9, rs9, alp, rvdw, c6, dc6dcn, d2c6dcn2, d2c6dcnij, hessian, dEdcn, dEdcndr, dEdcndcn, partition)

Analytical second derivatives of the three-body dispersion energy.

The triple energy is a function of the three squared distances and the three pairwise C6 coefficients. Contributions are accumulated as Cartesian second derivatives at fixed coordination number plus the derivatives with respect to the coordination numbers, which the caller contracts with dCN/dR.

Arguments

Type IntentOptional Attributes Name
class(structure_type), intent(in) :: mol

Molecular structure data

real(kind=wp), intent(in) :: trans(:,:)

Lattice points

real(kind=wp), intent(in) :: cutoff

Real space cutoff

real(kind=wp), intent(in) :: width

Width of smooth cutoff

real(kind=wp), intent(in) :: s9

Scaling for dispersion coefficients

real(kind=wp), intent(in) :: rs9

Scaling for van-der-Waals radii in damping function

real(kind=wp), intent(in) :: alp

Exponent of zero damping function

real(kind=wp), intent(in) :: rvdw(:,:)

Van-der-Waals radii for all element pairs

real(kind=wp), intent(in) :: c6(:,:)

C6 coefficients for all atom pairs.

real(kind=wp), intent(in) :: dc6dcn(:,:)

Derivatives of the C6 w.r.t. the coordination number

real(kind=wp), intent(in) :: d2c6dcn2(:,:)

Derivatives of the C6 w.r.t. the coordination number

real(kind=wp), intent(in) :: d2c6dcnij(:,:)

Derivatives of the C6 w.r.t. the coordination number

real(kind=wp), intent(inout) :: hessian(:,:)

Second derivative of the energy w.r.t. the Cartesian coordinates

real(kind=wp), intent(inout) :: dEdcn(:)

Derivative of the energy w.r.t. the coordination number

real(kind=wp), intent(inout) :: dEdcndr(:,:)

Mixed derivative w.r.t. coordination number and Cartesian coordinates

real(kind=wp), intent(inout) :: dEdcndcn(:,:)

Second derivative w.r.t. the coordination numbers

type(work_partition), intent(in), optional :: partition

Work partition of the atom pairs, absent selects the complete work


Source Code

subroutine get_atm_dispersion_hessian(mol, trans, cutoff, width, s9, rs9, alp, rvdw, &
      & c6, dc6dcn, d2c6dcn2, d2c6dcnij, hessian, dEdcn, dEdcndr, dEdcndcn, partition)

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

   !> Lattice points
   real(wp), intent(in) :: trans(:, :)

   !> Real space cutoff
   real(wp), intent(in) :: cutoff

   !> Width of smooth cutoff
   real(wp), intent(in) :: width

   !> Scaling for dispersion coefficients
   real(wp), intent(in) :: s9

   !> Scaling for van-der-Waals radii in damping function
   real(wp), intent(in) :: rs9

   !> Exponent of zero damping function
   real(wp), intent(in) :: alp

   !> Van-der-Waals radii for all element pairs
   real(wp), intent(in) :: rvdw(:, :)

   !> C6 coefficients for all atom pairs.
   real(wp), intent(in) :: c6(:, :)

   !> Derivatives of the C6 w.r.t. the coordination number
   real(wp), intent(in) :: dc6dcn(:, :), d2c6dcn2(:, :), d2c6dcnij(:, :)

   !> Second derivative of the energy w.r.t. the Cartesian coordinates
   real(wp), intent(inout) :: hessian(:, :)

   !> Derivative of the energy w.r.t. the coordination number
   real(wp), intent(inout) :: dEdcn(:)

   !> Mixed derivative w.r.t. coordination number and Cartesian coordinates
   real(wp), intent(inout) :: dEdcndr(:, :)

   !> Second derivative w.r.t. the coordination numbers
   real(wp), intent(inout) :: dEdcndcn(:, :)

   !> Work partition of the atom pairs, absent selects the complete work
   type(work_partition), intent(in), optional :: partition

   integer :: iat, jat, kat, izp, jzp, kzp, jtr, ktr
   integer :: ip, iq, il, im, ia, ib, ic, jc, ie, if_, nent
   integer :: at(3), pat(2, 3), ent_atom(6), ent_pair(6)
   real(wp) :: vec(3, 3), u(3), cutoff2, triple, r0, alp3, aexp, cval
   real(wp) :: swp(3), dswp(3), d2swp(3), sval, sp(3), spq(3, 3)
   real(wp) :: ww, wp_(3), wpq(3, 3), lval(3), nval, np(3), npq(3, 3)
   real(wp) :: gv, gd, gdd, hv, hd, hdd, ang, angp(3), angpq(3, 3)
   real(wp) :: tval, tp(3), tpq(3, 3), fd, fdp(3), fdpq(3, 3)
   real(wp) :: av, ap(3), apq(3, 3), kv, kp(3), kpq(3, 3)
   real(wp) :: c6t(3), qv, qm(3), qmn(3, 3), pref, ec(3), ecc(3, 3), euc(3, 3)
   real(wp) :: ent_coef(6), grad(3, 3, 3), tmp
   real(wp) :: gk(3, 3, 3), egr(3, 3, 3), dg(3, 3), pq
   integer, parameter :: sigp(3, 3) = reshape(&
      & [-1, 1, 0, -1, 0, 1, 0, -1, 1], [3, 3])
   real(wp), parameter :: cl(3, 3) = reshape(&
      & [1.0_wp, 1.0_wp, -1.0_wp, -1.0_wp, 1.0_wp, 1.0_wp, 1.0_wp, -1.0_wp, 1.0_wp], &
      & [3, 3])

   ! Thread-private arrays for reduction, these are O(N^2) unlike the gradient
   real(wp), allocatable :: hessian_local(:, :), dEdcn_local(:)
   real(wp), allocatable :: dEdcndr_local(:, :), dEdcndcn_local(:, :)
   type(work_partition) :: part

   if (abs(s9) < epsilon(1.0_wp)) return
   cutoff2 = cutoff*cutoff
   alp3 = alp / 3.0_wp
   aexp = 0.5_wp * alp3

   !$omp parallel default(none) &
   !$omp shared(mol, trans, cutoff, width, s9, rs9, rvdw, c6, dc6dcn, &
   !$omp& d2c6dcn2, d2c6dcnij, cutoff2, alp3, aexp, partition) &
   !$omp private(iat, jat, kat, izp, jzp, kzp, jtr, ktr, ip, iq, il, im, &
   !$omp& ia, ib, ic, jc, ie, if_, nent, at, pat, ent_atom, ent_pair, &
   !$omp& vec, u, triple, r0, cval, swp, dswp, d2swp, sval, sp, spq, &
   !$omp& ww, wp_, wpq, lval, nval, np, npq, gv, gd, gdd, hv, hd, hdd, &
   !$omp& ang, angp, angpq, tval, tp, tpq, fd, fdp, fdpq, av, ap, apq, &
   !$omp& kv, kp, kpq, c6t, qv, qm, qmn, pref, ec, ecc, euc, ent_coef, &
   !$omp& grad, tmp, gk, egr, dg, pq) &
   !$omp shared(hessian, dEdcn, dEdcndr, dEdcndcn) &
   !$omp private(hessian_local, dEdcn_local, dEdcndr_local, dEdcndcn_local)
   allocate(hessian_local(size(hessian, 1), size(hessian, 2)), source=0.0_wp)
   allocate(dEdcn_local(size(dEdcn, 1)), source=0.0_wp)
   allocate(dEdcndr_local(size(dEdcndr, 1), size(dEdcndr, 2)), source=0.0_wp)
   allocate(dEdcndcn_local(size(dEdcndcn, 1), size(dEdcndcn, 2)), source=0.0_wp)
   ! the triple loop is strongly triangular, static scheduling would leave the
   ! last threads with most of the work
   !$omp do schedule(dynamic)
   do iat = 1, mol%nat
      izp = mol%id(iat)
      do jat = 1, iat
         if (.not.owns_pair(partition, iat, jat)) cycle
         jzp = mol%id(jat)
         do jtr = 1, size(trans, 2)
            vec(:, 1) = mol%xyz(:, jat) + trans(:, jtr) - mol%xyz(:, iat)
            u(1) = sum(vec(:, 1)**2)
            if (u(1) > cutoff2 .or. u(1) < epsilon(1.0_wp)) cycle
            do kat = 1, jat
               kzp = mol%id(kat)
               triple = triple_scale(iat, jat, kat)
               r0 = rs9*rvdw(jzp, izp) * rs9*rvdw(kzp, izp) * rs9*rvdw(kzp, jzp)
               do ktr = 1, size(trans, 2)
                  vec(:, 2) = mol%xyz(:, kat) + trans(:, ktr) - mol%xyz(:, iat)
                  u(2) = sum(vec(:, 2)**2)
                  if (u(2) > cutoff2 .or. u(2) < epsilon(1.0_wp)) cycle
                  vec(:, 3) = vec(:, 2) - vec(:, 1)
                  u(3) = sum(vec(:, 3)**2)
                  if (u(3) > cutoff2 .or. u(3) < epsilon(1.0_wp)) cycle

                  c6t(1) = c6(jat, iat)
                  c6t(2) = c6(kat, iat)
                  c6t(3) = c6(kat, jat)
                  if (any(abs(c6t) < epsilon(1.0_wp))) cycle

                  ! switching function and its derivatives w.r.t. the squared distances
                  do ip = 1, 3
                     call smooth_cutoff_r2(u(ip), cutoff, width, swp(ip), dswp(ip), d2swp(ip))
                  end do
                  sval = swp(1)*swp(2)*swp(3)
                  sp(1) = dswp(1)*swp(2)*swp(3)
                  sp(2) = swp(1)*dswp(2)*swp(3)
                  sp(3) = swp(1)*swp(2)*dswp(3)
                  spq(1, 1) = d2swp(1)*swp(2)*swp(3)
                  spq(2, 2) = swp(1)*d2swp(2)*swp(3)
                  spq(3, 3) = swp(1)*swp(2)*d2swp(3)
                  spq(1, 2) = dswp(1)*dswp(2)*swp(3)
                  spq(2, 1) = spq(1, 2)
                  spq(1, 3) = dswp(1)*swp(2)*dswp(3)
                  spq(3, 1) = spq(1, 3)
                  spq(2, 3) = swp(1)*dswp(2)*dswp(3)
                  spq(3, 2) = spq(2, 3)

                  ! product of the squared distances
                  ww = u(1)*u(2)*u(3)
                  wp_(1) = u(2)*u(3)
                  wp_(2) = u(1)*u(3)
                  wp_(3) = u(1)*u(2)
                  wpq(:, :) = 0.0_wp
                  wpq(1, 2) = u(3); wpq(2, 1) = u(3)
                  wpq(1, 3) = u(2); wpq(3, 1) = u(2)
                  wpq(2, 3) = u(1); wpq(3, 2) = u(1)

                  ! triple product entering the angular term
                  lval(1) = u(1) + u(3) - u(2)
                  lval(2) = u(1) - u(3) + u(2)
                  lval(3) = -u(1) + u(3) + u(2)
                  nval = lval(1)*lval(2)*lval(3)
                  do ip = 1, 3
                     np(ip) = cl(1, ip)*lval(2)*lval(3) + cl(2, ip)*lval(1)*lval(3) &
                        & + cl(3, ip)*lval(1)*lval(2)
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        tmp = 0.0_wp
                        do il = 1, 3
                           do im = 1, 3
                              if (il == im) cycle
                              ! remaining index of the product
                              tmp = tmp + cl(il, ip)*cl(im, iq)*lval(6 - il - im)
                           end do
                        end do
                        npq(ip, iq) = tmp
                     end do
                  end do

                  gv = ww**(-2.5_wp)
                  gd = -2.5_wp * ww**(-3.5_wp)
                  gdd = 8.75_wp * ww**(-4.5_wp)
                  hv = ww**(-1.5_wp)
                  hd = -1.5_wp * ww**(-2.5_wp)
                  hdd = 3.75_wp * ww**(-3.5_wp)

                  ang = 0.375_wp*nval*gv + hv
                  do ip = 1, 3
                     angp(ip) = 0.375_wp*(np(ip)*gv + nval*gd*wp_(ip)) + hd*wp_(ip)
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        angpq(ip, iq) = 0.375_wp*(npq(ip, iq)*gv &
                           & + np(ip)*gd*wp_(iq) + np(iq)*gd*wp_(ip) &
                           & + nval*(gdd*wp_(ip)*wp_(iq) + gd*wpq(ip, iq))) &
                           & + hdd*wp_(ip)*wp_(iq) + hd*wpq(ip, iq)
                     end do
                  end do

                  ! zero damping function
                  cval = 6.0_wp * r0**alp3
                  tval = cval * ww**(-aexp)
                  do ip = 1, 3
                     tp(ip) = -aexp*tval*wp_(ip)/ww
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        tpq(ip, iq) = aexp*(aexp + 1.0_wp)*tval*wp_(ip)*wp_(iq)/(ww*ww) &
                           & - aexp*tval*wpq(ip, iq)/ww
                     end do
                  end do
                  fd = 1.0_wp/(1.0_wp + tval)
                  do ip = 1, 3
                     fdp(ip) = -tp(ip)*fd*fd
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        fdpq(ip, iq) = -tpq(ip, iq)*fd*fd + 2.0_wp*tp(ip)*tp(iq)*fd**3
                     end do
                  end do

                  av = ang*fd
                  do ip = 1, 3
                     ap(ip) = angp(ip)*fd + ang*fdp(ip)
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        apq(ip, iq) = angpq(ip, iq)*fd + angp(ip)*fdp(iq) &
                           & + angp(iq)*fdp(ip) + ang*fdpq(ip, iq)
                     end do
                  end do

                  kv = sval*av
                  do ip = 1, 3
                     kp(ip) = sp(ip)*av + sval*ap(ip)
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        kpq(ip, iq) = spq(ip, iq)*av + sp(ip)*ap(iq) &
                           & + sp(iq)*ap(ip) + sval*apq(ip, iq)
                     end do
                  end do

                  ! geometric mean of the C6 coefficients
                  qv = sqrt(abs(c6t(1)*c6t(2)*c6t(3)))
                  do ip = 1, 3
                     qm(ip) = 0.5_wp*qv/c6t(ip)
                  end do
                  do ip = 1, 3
                     do iq = 1, 3
                        if (ip == iq) then
                           qmn(ip, iq) = -0.25_wp*qv/(c6t(ip)*c6t(ip))
                        else
                           qmn(ip, iq) = 0.25_wp*qv/(c6t(ip)*c6t(iq))
                        end if
                     end do
                  end do

                  pref = s9*triple

                  at(1) = iat; at(2) = jat; at(3) = kat
                  do ia = 1, 3
                     do ip = 1, 3
                        grad(:, ia, ip) = 2.0_wp*sigp(ia, ip)*vec(:, ip)
                     end do
                  end do

                  ! Cartesian second derivatives at fixed coordination number
                  pq = pref*qv
                  ! contract with the pair gradients once instead of per component pair
                  do ia = 1, 3
                     do ic = 1, 3
                        do iq = 1, 3
                           tmp = 0.0_wp
                           do ip = 1, 3
                              tmp = tmp + kpq(ip, iq)*grad(ic, ia, ip)
                           end do
                           gk(ic, ia, iq) = pq*tmp
                        end do
                     end do
                  end do
                  do ia = 1, 3
                     do ib = 1, 3
                        tmp = 0.0_wp
                        do ip = 1, 3
                           tmp = tmp + kp(ip)*sigp(ia, ip)*sigp(ib, ip)
                        end do
                        dg(ia, ib) = 2.0_wp*pq*tmp
                     end do
                  end do

                  do ia = 1, 3
                     do ib = 1, 3
                        do ic = 1, 3
                           do jc = 1, 3
                              tmp = 0.0_wp
                              do iq = 1, 3
                                 tmp = tmp + gk(ic, ia, iq)*grad(jc, ib, iq)
                              end do
                              if (ic == jc) tmp = tmp + dg(ia, ib)
                              hessian_local(3*(at(ia)-1)+ic, 3*(at(ib)-1)+jc) = &
                                 & hessian_local(3*(at(ia)-1)+ic, 3*(at(ib)-1)+jc) + tmp
                           end do
                        end do
                     end do
                  end do

                  ! derivatives with respect to the C6 coefficients
                  do ip = 1, 3
                     ec(ip) = pref*qm(ip)*kv
                     do iq = 1, 3
                        ecc(ip, iq) = pref*qmn(ip, iq)*kv
                        euc(iq, ip) = pref*qm(ip)*kp(iq)
                     end do
                  end do

                  ! contract the mixed CN/Cartesian derivatives once per pair
                  do ia = 1, 3
                     do ic = 1, 3
                        do im = 1, 3
                           tmp = 0.0_wp
                           do ip = 1, 3
                              tmp = tmp + euc(ip, im)*grad(ic, ia, ip)
                           end do
                           egr(ic, ia, im) = tmp
                        end do
                     end do
                  end do

                  pat(1, 1) = iat; pat(2, 1) = jat
                  pat(1, 2) = iat; pat(2, 2) = kat
                  pat(1, 3) = jat; pat(2, 3) = kat

                  nent = 0
                  do ip = 1, 3
                     if (pat(1, ip) /= pat(2, ip)) then
                        nent = nent + 1
                        ent_atom(nent) = pat(1, ip)
                        ent_pair(nent) = ip
                        ent_coef(nent) = dc6dcn(pat(1, ip), pat(2, ip))
                        nent = nent + 1
                        ent_atom(nent) = pat(2, ip)
                        ent_pair(nent) = ip
                        ent_coef(nent) = dc6dcn(pat(2, ip), pat(1, ip))
                     else
                        nent = nent + 1
                        ent_atom(nent) = pat(1, ip)
                        ent_pair(nent) = ip
                        ent_coef(nent) = 2.0_wp*dc6dcn(pat(1, ip), pat(1, ip))
                     end if
                  end do

                  do ie = 1, nent
                     dEdcn_local(ent_atom(ie)) = dEdcn_local(ent_atom(ie)) &
                        & + ec(ent_pair(ie))*ent_coef(ie)
                     do if_ = 1, nent
                        dEdcndcn_local(ent_atom(ie), ent_atom(if_)) = &
                           & dEdcndcn_local(ent_atom(ie), ent_atom(if_)) &
                           & + ecc(ent_pair(ie), ent_pair(if_))*ent_coef(ie)*ent_coef(if_)
                     end do
                     do ia = 1, 3
                        do ic = 1, 3
                           dEdcndr_local(3*(at(ia)-1)+ic, ent_atom(ie)) = &
                              & dEdcndr_local(3*(at(ia)-1)+ic, ent_atom(ie)) &
                              & + egr(ic, ia, ent_pair(ie))*ent_coef(ie)
                        end do
                     end do
                  end do

                  ! second derivative of the C6 coefficients w.r.t. the coordination numbers
                  do ip = 1, 3
                     ia = pat(1, ip)
                     ib = pat(2, ip)
                     if (ia /= ib) then
                        dEdcndcn_local(ia, ia) = dEdcndcn_local(ia, ia) + ec(ip)*d2c6dcn2(ia, ib)
                        dEdcndcn_local(ib, ib) = dEdcndcn_local(ib, ib) + ec(ip)*d2c6dcn2(ib, ia)
                        dEdcndcn_local(ia, ib) = dEdcndcn_local(ia, ib) + ec(ip)*d2c6dcnij(ia, ib)
                        dEdcndcn_local(ib, ia) = dEdcndcn_local(ib, ia) + ec(ip)*d2c6dcnij(ia, ib)
                     else
                        dEdcndcn_local(ia, ia) = dEdcndcn_local(ia, ia) + ec(ip) &
                           & * (2.0_wp*d2c6dcn2(ia, ia) + 2.0_wp*d2c6dcnij(ia, ia))
                     end if
                  end do
               end do
            end do
         end do
      end do
   end do
   !$omp end do
   !$omp critical (get_atm_dispersion_hessian_)
   hessian(:, :) = hessian(:, :) + hessian_local(:, :)
   dEdcn(:) = dEdcn(:) + dEdcn_local(:)
   dEdcndr(:, :) = dEdcndr(:, :) + dEdcndr_local(:, :)
   dEdcndcn(:, :) = dEdcndcn(:, :) + dEdcndcn_local(:, :)
   !$omp end critical (get_atm_dispersion_hessian_)
   deallocate(hessian_local, dEdcn_local, dEdcndr_local, dEdcndcn_local)
   !$omp end parallel

end subroutine get_atm_dispersion_hessian