Evaluate the two-body dispersion energy on a particle mesh
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(structure_type), | intent(in) | :: | mol |
Molecular structure data |
||
| class(d3_lowrank_c6), | intent(in) | :: | lowrank |
Separable representation of the C6 coefficients |
||
| logical, | intent(in) | :: | ghost(:) |
Atoms excluded from the dispersion calculation |
||
| type(fourier_term), | intent(in) | :: | terms(:,:,:) |
Terms of the damped pair potential for each pair of species |
||
| integer, | intent(in) | :: | nterm(:,:) |
Number of terms for each pair of species |
||
| integer, | intent(in) | :: | mesh(3) |
Number of mesh points along each direction |
||
| real(kind=wp), | intent(in) | :: | gwvec(:,:) |
Weighting function for the atomic reference systems |
||
| real(kind=wp), | intent(in), | optional | :: | gwdcn(:,:) |
Derivative of the weighting function w.r.t. the coordination number |
|
| real(kind=wp), | intent(inout) | :: | energies(:) |
Dispersion energy |
||
| real(kind=wp), | intent(inout), | optional | :: | dEdcn(:) |
Derivative of the energy w.r.t. the coordination number |
|
| real(kind=wp), | intent(inout), | optional | :: | gradient(:,:) |
Dispersion gradient |
|
| real(kind=wp), | intent(inout), | optional | :: | sigma(:,:) |
Dispersion virial |
|
| type(work_partition), | intent(in), | optional | :: | partition |
Work partition over the terms of the low-rank expansion |
subroutine get_dispersion_spme(mol, lowrank, ghost, terms, nterm, mesh, gwvec, & & gwdcn, energies, dEdcn, gradient, sigma, partition) !> Molecular structure data class(structure_type), intent(in) :: mol !> Separable representation of the C6 coefficients class(d3_lowrank_c6), intent(in) :: lowrank !> Atoms excluded from the dispersion calculation logical, intent(in) :: ghost(:) !> Terms of the damped pair potential for each pair of species type(fourier_term), intent(in) :: terms(:, :, :) !> Number of terms for each pair of species integer, intent(in) :: nterm(:, :) !> Number of mesh points along each direction integer, intent(in) :: mesh(3) !> Weighting function for the atomic reference systems real(wp), intent(in) :: gwvec(:, :) !> Derivative of the weighting function w.r.t. the coordination number real(wp), intent(in), optional :: gwdcn(:, :) !> Dispersion energy real(wp), intent(inout) :: energies(:) !> Derivative of the energy w.r.t. the coordination number real(wp), intent(inout), optional :: dEdcn(:) !> Dispersion gradient real(wp), intent(inout), optional :: gradient(:, :) !> Dispersion virial real(wp), intent(inout), optional :: sigma(:, :) !> Work partition over the terms of the low-rank expansion type(work_partition), intent(in), optional :: partition logical :: grad type(fft_mesh) :: fmesh integer :: nat, nid, rank, iat, izp, jzp, il, it, ic, jc integer :: i1, i2, i3, j1, j2, j3, g1, g2, g3 real(wp) :: vol, rec(3, 3), invlat(3, 3), kvec(3), knorm, erecip real(wp) :: pval, dpval, phi, dphi, wgt, dwgt(3), sfac, qq, escale real(wp) :: dudr(3, 3), tvec(3), dtvec(3) integer, allocatable :: base(:, :) real(wp), allocatable :: theta(:, :, :), dtheta(:, :, :) real(wp), allocatable :: bfac(:, :), c6l(:, :), dc6ldcn(:, :) real(wp), allocatable :: phik(:, :, :, :), dphik(:, :, :, :) real(wp), allocatable :: pot(:, :), esum(:), dsum(:), gsum(:, :), ssum(:, :) complex(wp), allocatable :: qgrid(:, :, :, :), tgrid(:, :, :) grad = present(gwdcn) .and. present(dEdcn) .and. present(gradient) & & .and. present(sigma) nat = mol%nat nid = mol%nid rank = lowrank%rank invlat(:, :) = matinv_3x3(mol%lattice) rec(:, :) = 2.0_wp * pi * transpose(invlat) vol = abs(matdet_3x3(mol%lattice)) allocate(c6l(rank, nat)) allocate(dc6ldcn(rank, nat), source=0.0_wp) if (grad) then call lowrank%get_weights(mol, ghost, gwvec, c6l, gwdcn, dc6ldcn) else call lowrank%get_weights(mol, ghost, gwvec, c6l) end if allocate(esum(nat), dsum(nat), source=0.0_wp) allocate(gsum(3, nat), source=0.0_wp) allocate(ssum(3, 3), source=0.0_wp) ! the unrestricted double sum contains the interaction of every atom with ! itself, which the value of the potential at the origin removes again do iat = 1, nat izp = mol%id(iat) pval = 0.0_wp do it = 1, nterm(izp, izp) pval = pval + get_potential_zero(terms(it, izp, izp)) end do do il = 1, rank if (.not.owns_index(partition, il)) cycle esum(iat) = esum(iat) & & + 0.5_wp * lowrank%lambda(il) * c6l(il, iat)**2 * pval dsum(iat) = dsum(iat) & & + lowrank%lambda(il) * c6l(il, iat) * dc6ldcn(il, iat) * pval end do end do call new_fft_mesh(fmesh, mesh) call get_spline_weights(mol, mesh, base, theta, dtheta) call get_euler_factors(mesh, bfac) ! the transform of the pair potential depends on the species pair but not on ! the term of the expansion, so it is tabulated once for the whole mesh call get_mesh_kernel(mesh, rec, terms, nterm, nid, grad, phik, dphik) do ic = 1, 3 do jc = 1, 3 dudr(jc, ic) = mesh(ic) * rec(jc, ic) / (2.0_wp * pi) end do end do allocate(qgrid(mesh(1), mesh(2), mesh(3), nid)) allocate(tgrid(mesh(1), mesh(2), mesh(3))) allocate(pot(nat, nid)) erecip = 0.0_wp escale = -0.5_wp / vol ! the terms of the expansion decouple, so only one set of meshes is live and ! a partition can hand out whole terms do il = 1, rank if (.not.owns_index(partition, il)) cycle qgrid(:, :, :, :) = cmplx(0.0_wp, 0.0_wp, wp) do iat = 1, nat if (ghost(iat)) cycle izp = mol%id(iat) do j3 = 1, spline_order g3 = modulo(base(3, iat) + j3, mesh(3)) + 1 do j2 = 1, spline_order g2 = modulo(base(2, iat) + j2, mesh(2)) + 1 do j1 = 1, spline_order g1 = modulo(base(1, iat) + j1, mesh(1)) + 1 wgt = theta(j1, 1, iat) * theta(j2, 2, iat) * theta(j3, 3, iat) qgrid(g1, g2, g3, izp) = qgrid(g1, g2, g3, izp) & & + cmplx(c6l(il, iat) * wgt, 0.0_wp, wp) end do end do end do end do do izp = 1, nid call fft_3d(fmesh, qgrid(:, :, :, izp), +1) end do ! contract the structure factors with the kernel to obtain the potential ! each species experiences, and accumulate energy and virial on the way do izp = 1, nid tgrid(:, :, :) = cmplx(0.0_wp, 0.0_wp, wp) !$omp parallel do schedule(runtime) default(none) collapse(2) & !$omp shared(mesh, nid, izp, phik, bfac, qgrid, tgrid) & !$omp private(i1, i2, i3, jzp, sfac) do i3 = 1, mesh(3) do i2 = 1, mesh(2) do i1 = 1, mesh(1) sfac = bfac(i1, 1) * bfac(i2, 2) * bfac(i3, 3) do jzp = 1, nid tgrid(i1, i2, i3) = tgrid(i1, i2, i3) + sfac & & * phik(pair_index(izp, jzp), i1, i2, i3) & & * qgrid(i1, i2, i3, jzp) end do end do end do end do call fft_3d(fmesh, tgrid, -1) ! gather the potential back onto the atoms of this species do iat = 1, nat pot(iat, izp) = 0.0_wp end do do iat = 1, nat if (ghost(iat) .or. mol%id(iat) /= izp) cycle wgt = 0.0_wp dwgt(:) = 0.0_wp do j3 = 1, spline_order g3 = modulo(base(3, iat) + j3, mesh(3)) + 1 do j2 = 1, spline_order g2 = modulo(base(2, iat) + j2, mesh(2)) + 1 do j1 = 1, spline_order g1 = modulo(base(1, iat) + j1, mesh(1)) + 1 sfac = real(tgrid(g1, g2, g3), wp) tvec(1) = theta(j1, 1, iat) tvec(2) = theta(j2, 2, iat) tvec(3) = theta(j3, 3, iat) wgt = wgt + tvec(1) * tvec(2) * tvec(3) * sfac if (grad) then dtvec(1) = dtheta(j1, 1, iat) * tvec(2) * tvec(3) dtvec(2) = tvec(1) * dtheta(j2, 2, iat) * tvec(3) dtvec(3) = tvec(1) * tvec(2) * dtheta(j3, 3, iat) dwgt(:) = dwgt(:) + matmul(dudr, dtvec) * sfac end if end do end do end do pot(iat, izp) = wgt esum(iat) = esum(iat) + escale * lowrank%lambda(il) * c6l(il, iat) * wgt erecip = erecip + escale * lowrank%lambda(il) * c6l(il, iat) * wgt if (grad) then dsum(iat) = dsum(iat) & & + 2.0_wp * escale * lowrank%lambda(il) * dc6ldcn(il, iat) * wgt gsum(:, iat) = gsum(:, iat) & & + 2.0_wp * escale * lowrank%lambda(il) * c6l(il, iat) * dwgt(:) end if end do end do if (grad) then !$omp parallel do schedule(runtime) default(none) collapse(2) & !$omp shared(mesh, nid, rec, bfac, dphik, qgrid, lowrank, il, vol) & !$omp private(i1, i2, i3, j1, j2, j3, izp, jzp, kvec, knorm, qq, sfac, ic) & !$omp reduction(+:ssum) do i3 = 1, mesh(3) do i2 = 1, mesh(2) do i1 = 1, mesh(1) j1 = i1 - 1 - mesh(1) * ((2*(i1 - 1)) / mesh(1)) j2 = i2 - 1 - mesh(2) * ((2*(i2 - 1)) / mesh(2)) j3 = i3 - 1 - mesh(3) * ((2*(i3 - 1)) / mesh(3)) kvec(:) = rec(:, 1)*j1 + rec(:, 2)*j2 + rec(:, 3)*j3 knorm = norm2(kvec) if (knorm <= 0.0_wp) cycle sfac = bfac(i1, 1) * bfac(i2, 2) * bfac(i3, 3) qq = 0.0_wp do izp = 1, nid do jzp = 1, nid qq = qq + lowrank%lambda(il) & & * dphik(pair_index(izp, jzp), i1, i2, i3) * sfac & & * real(qgrid(i1, i2, i3, izp) & & * conjg(qgrid(i1, i2, i3, jzp)), wp) end do end do do ic = 1, 3 ssum(ic, :) = ssum(ic, :) & & + 0.5_wp * qq * kvec(ic) * kvec(:) / (knorm * vol) end do end do end do end do end if end do energies(:) = energies(:) + esum(:) if (grad) then do ic = 1, 3 ssum(ic, ic) = ssum(ic, ic) - erecip end do dEdcn(:) = dEdcn(:) + dsum(:) gradient(:, :) = gradient(:, :) + gsum(:, :) sigma(:, :) = sigma(:, :) + ssum(:, :) end if end subroutine get_dispersion_spme