Skip to content

Commit 4aa68f8

Browse files
authored
Nonlocal term for dynamical matrix (#944)
1 parent d171a27 commit 4aa68f8

File tree

10 files changed

+295
-73
lines changed

10 files changed

+295
-73
lines changed

src/DFTK.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ include("common/norm.jl")
4444
include("common/quadrature.jl")
4545
include("common/hankel.jl")
4646
include("common/hydrogenic.jl")
47+
include("common/derivatives.jl")
4748

4849
export PspHgh
4950
export PspUpf

src/common/derivatives.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Helper: derivatives of a position-dependent function `f`. First-order derivatives for a
2+
# displacement of the atom `s` in the direction `α`.
3+
function derivative_wrt_αs(f, positions::AbstractVector{Vec3{T}}, α, s) where {T}
4+
displacement = zero.(positions)
5+
displacement[s] = setindex(displacement[s], one(T), α)
6+
ForwardDiff.derivative(zero(T)) do ε
7+
f*displacement .+ positions)
8+
end
9+
end

src/densities.jl

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,19 @@ end
7070
end
7171
range = [(ik, n) for ik = 1:length(basis.kpoints) for n = mask_occ[ik]]
7272

73-
k_to_k_plus_q = k_to_kpq_permutation(basis, q)
73+
# The variation of the orbital ψ_k defined in the basis ℬ_k is δψ_{[k+q]} in ℬ_{[k+q]},
74+
# where [k+q] is equivalent to the basis k+q (see find_equivalent_kpt).
75+
# The perturbation of the density
76+
# |ψ_{n,k}|² is 2 ψ_{n,k} * δψ_{n,k+q}.
77+
# Hence, we first get the δψ_{[k+q]} as δψ_{k+q}…
78+
δψ_plus_k = transfer_blochwave_equivalent_to_actual(basis, δψ, q)
7479
storages = parallel_loop_over_range(allocate_local_storage, range) do storage, kn
7580
(ik, n) = kn
7681

7782
kpt = basis.kpoints[ik]
7883
ifft!(storage.ψnk_real, basis, kpt, ψ[ik][:, n])
79-
# We return the δψk in the basis k+q which are associated to a displacement of the ψk.
80-
kpt_plus_q, equivalent_kpt_plus_q = get_kpoint(basis, kpt.coordinate + q, kpt.spin)
81-
δψk_plus_q = transfer_blochwave_kpt(δψ[k_to_k_plus_q[ik]], basis,
82-
equivalent_kpt_plus_q, basis, kpt_plus_q)
83-
# The perturbation of the density
84-
# |ψ_nk|² is 2 ψ_{n,k} * δψ_{n,k+q}.
85-
ifft!(storage.δψnk_real, basis, kpt_plus_q, δψk_plus_q[:, n])
84+
# … and then we compute the real Fourier transform in the adequate basis.
85+
ifft!(storage.δψnk_real, basis, δψ_plus_k[ik].kpt, δψ_plus_k[ik].ψk[:, n])
8686

8787
storage.δρ[:, :, :, kpt.spin] .+= real_qzero(
8888
2 .* occupation[ik][n] .* basis.kweights[ik] .* conj(storage.ψnk_real)
@@ -94,9 +94,7 @@ end
9494
δρ = sum(getfield.(storages, :δρ))
9595

9696
mpi_sum!(δρ, basis.comm_kpts)
97-
δρ = symmetrize_ρ(basis, δρ; do_lowpass=false)
98-
99-
δρ
97+
symmetrize_ρ(basis, δρ; do_lowpass=false)
10098
end
10199

102100
@views @timing function compute_kinetic_energy_density(basis::PlaneWaveBasis, ψ, occupation)
@@ -106,7 +104,7 @@ end
106104
dαψnk_real = zeros(complex(eltype(basis)), basis.fft_size)
107105
for (ik, kpt) in enumerate(basis.kpoints)
108106
G_plus_k = [[p[α] for p in Gplusk_vectors_cart(basis, kpt)] for α = 1:3]
109-
for n in axes(ψ[ik], 2), α = 1:3
107+
for n = 1:size(ψ[ik], 2), α = 1:3
110108
ifft!(dαψnk_real, basis, kpt, im .* G_plus_k[α] .* ψ[ik][:, n])
111109
@. τ[:, :, :, kpt.spin] += occupation[ik][n] * basis.kweights[ik] / 2 * abs2(dαψnk_real)
112110
end

src/postprocess/phonon.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function dynmat_red_to_cart(model::Model, dynmat)
77
# We have δr_cart = lattice * δr_red, δF_cart = lattice⁻ᵀ δF_red, so
88
# δF_cart = lattice⁻ᵀ D_red lattice⁻¹ δr_cart
99
dynmat_cart = zero.(dynmat)
10-
for s in axes(dynmat_cart, 2), α in axes(dynmat_cart, 4)
10+
for s = 1:size(dynmat_cart, 2), α = 1:size(dynmat_cart, 4)
1111
dynmat_cart[:, α, :, s] = inv_lattice' * dynmat[:, α, :, s] * inv_lattice
1212
end
1313
dynmat_cart
@@ -37,7 +37,7 @@ function phonon_modes(basis::PlaneWaveBasis{T}, ψ, occupation; kwargs...) where
3737

3838
modes = _phonon_modes(basis, dynmat_cart)
3939
vectors = similar(modes.vectors_cart)
40-
for s in axes(vectors, 2), t in axes(vectors, 4)
40+
for s = 1:size(vectors, 2), t = 1:size(vectors, 4)
4141
vectors[:, s, :, t] = vector_cart_to_red(basis.model, modes.vectors_cart[:, s, :, t])
4242
end
4343

src/terms/ewald.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ function compute_dynmat(ewald::TermEwald, basis::PlaneWaveBasis{T}, ψ, occupati
230230
n_dim = model.n_dim
231231
charges = T.(charge_ionic.(model.atoms))
232232

233-
dynmat = zeros(complex(T), n_dim, n_atoms, n_dim, n_atoms)
233+
dynmat = zeros(complex(T), 3, n_atoms, 3, n_atoms)
234234
# Real part
235235
for s = 1:n_atoms, α = 1:n_dim
236236
displacement = zero.(model.positions)

src/terms/local.jl

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function compute_local_potential(basis::PlaneWaveBasis{T}; positions=basis.model
9696
# Pre-compute the form factors at unique values of |G| to speed up
9797
# the potential Fourier transform (by a lot). Using a hash map gives O(1)
9898
# lookup.
99-
form_factors = IdDict{Tuple{Int,T},T}() # IdDict for Dual compatability
99+
form_factors = IdDict{Tuple{Int,T},T}() # IdDict for Dual compatibility
100100
for G in Gqs_cart
101101
p = norm(G)
102102
for (igroup, group) in enumerate(model.atom_groups)
@@ -158,62 +158,46 @@ end
158158
forces
159159
end
160160

161-
# Phonon: Perturbation of the local potential with respect to a displacement on the
162-
# direction α of the atom s.
163-
function compute_δV_αs(::TermAtomicLocal, basis::PlaneWaveBasis{T}, α, s; q=zero(Vec3{T}),
164-
positions=basis.model.positions) where {T}
165-
S = iszero(q) ? T : complex(T)
166-
displacement = zero.(positions)
167-
displacement[s] = setindex(displacement[s], one(T), α)
168-
ForwardDiff.derivative(zero(T)) do ε
169-
positions = ε*displacement .+ positions
170-
compute_local_potential(basis; q, positions)
171-
end
172-
end
173-
174-
# Phonon: Second-order perturbation of the local potential with respect to a displacement on
175-
# the directions α and β of the atoms s and t.
176-
function compute_δ²V_βtαs(term::TermAtomicLocal, basis::PlaneWaveBasis{T}, β, t, α, s) where {T}
177-
positions = basis.model.positions
178-
displacement = zero.(positions)
179-
displacement[t] = setindex(displacement[t], one(T), β)
180-
ForwardDiff.derivative(zero(T)) do ε
181-
positions = ε*displacement .+ positions
182-
compute_δV_αs(term, basis, α, s; positions)
183-
end
184-
end
185-
186-
function compute_dynmat(term::TermAtomicLocal, basis::PlaneWaveBasis{T}, ψ, occupation; ρ,
187-
δρs, q=zero(Vec3{T}), kwargs...) where {T}
161+
@views function compute_dynmat(term::TermAtomicLocal, basis::PlaneWaveBasis{T}, ψ, occupation;
162+
ρ, δρs, q=zero(Vec3{T}), kwargs...) where {T}
188163
S = complex(T)
189164
model = basis.model
190165
positions = model.positions
191166
n_atoms = length(positions)
192167
n_dim = model.n_dim
193168

194-
∫δρδV = zeros(S, 3, n_atoms, 3, n_atoms)
169+
# Two contributions: dynmat_δH and dynmat_δ²H
170+
171+
# dynmat_δH, which is ∫δρδV.
172+
dynmat_δH = zeros(S, 3, n_atoms, 3, n_atoms)
195173
for s = 1:n_atoms, α = 1:n_dim
196-
∫δρδV[:, :, α, s] .-= stack(forces_local(S, basis, δρs[α, s], q))
174+
dynmat_δH[:, :, α, s] .-= stack(forces_local(S, basis, δρs[α, s], q))
197175
end
198176

199-
∫ρδ²V = zeros(S, 3, n_atoms, 3, n_atoms)
177+
# dynmat_δ²H, which is ∫ρδ²V.
178+
dynmat_δ²H = zeros(S, 3, n_atoms, 3, n_atoms)
200179
ρ_fourier = fft(basis, total_density(ρ))
201180
δ²V_fourier = similar(ρ_fourier)
202-
for s = 1:n_atoms, α = 1:n_dim
203-
for t = 1:n_atoms, β = 1:n_dim
204-
fft!(δ²V_fourier, basis, compute_δ²V_βtαs(term, basis, β, t, α, s))
205-
∫ρδ²V[β, t, α, s] = sum(conj(ρ_fourier) .* δ²V_fourier)
181+
for s = 1:n_atoms, α = 1:n_dim, β = 1:n_dim # zero if s ≠ t
182+
δ²V = derivative_wrt_αs(basis.model.positions, β, s) do positions_βs
183+
derivative_wrt_αs(positions_βs, α, s) do positions_βsαs
184+
compute_local_potential(basis; positions=positions_βsαs)
185+
end
206186
end
187+
dynmat_δ²H[β, s, α, s] += sum(conj(ρ_fourier) .* fft!(δ²V_fourier, basis, δ²V))
207188
end
208189

209-
∫δρδV + ∫ρδ²V
190+
dynmat_δH + dynmat_δ²H
210191
end
211192

212193
# δH is the perturbation of the local potential due to a position displacement e^{iq·r} of
213194
# the α coordinate of atom s.
214195
function compute_δHψ_αs(term::TermAtomicLocal, basis::PlaneWaveBasis, ψ, α, s, q)
215196
δV_αs = similar(ψ[1], basis.fft_size..., basis.model.n_spin_components)
216-
# All spin components get the same potential.
217-
δV_αs .= compute_δV_αs(term, basis, α, s; q)
197+
# Perturbation of the local potential with respect to a displacement on the direction α
198+
# of the atom s. All spin components get the same.
199+
δV_αs .= derivative_wrt_αs(basis.model.positions, α, s) do positions_αs
200+
compute_local_potential(basis; q, positions=positions_αs)
201+
end
218202
multiply_ψ_by_blochwave(basis, ψ, δV_αs, q)
219203
end

src/terms/nonlocal.jl

Lines changed: 127 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,19 @@ end
5555
psp_groups = [group for group in model.atom_groups
5656
if model.atoms[first(group)] isa ElementPsp]
5757

58-
# early return if no pseudopotential atoms
58+
# Early return if no pseudopotential atoms.
5959
isempty(psp_groups) && return nothing
6060

61-
# energy terms are of the form <psi, P C P' psi>, where P(G) = form_factor(G) * structure_factor(G)
61+
# Energy terms are of the form <ψ, P C P' ψ>, where
62+
# P(G) = form_factor(G) * structure_factor(G).
6263
forces = [zero(Vec3{T}) for _ = 1:length(model.positions)]
64+
6365
for group in psp_groups
6466
element = model.atoms[first(group)]
6567

6668
C = build_projection_coefficients(T, element.psp)
6769
for (ik, kpt) in enumerate(basis.kpoints)
68-
# we compute the forces from the irreductible BZ; they are symmetrized later
70+
# We compute the forces from the irreductible BZ; they are symmetrized later.
6971
G_plus_k = Gplusk_vectors(basis, kpt)
7072
G_plus_k_cart = to_cpu(Gplusk_vectors_cart(basis, kpt))
7173
form_factors = build_form_factors(element.psp, G_plus_k_cart)
@@ -77,9 +79,9 @@ end
7779
forces[idx] += map(1:3) do α
7880
dPdR = [-2T(π)*im*p[α] for p in G_plus_k] .* P
7981
ψk = ψ[ik]
80-
dHψk = P * (C * (dPdR' * ψk))
82+
δHψk = P * (C * (dPdR' * ψk))
8183
-sum(occupation[ik][iband] * basis.kweights[ik] *
82-
2real(dot(ψk[:, iband], dHψk[:, iband]))
84+
2real(dot(ψk[:, iband], δHψk[:, iband]))
8385
for iband=1:size(ψk, 2))
8486
end # α
8587
end # r
@@ -155,11 +157,12 @@ where ``\widehat{\rm proj}_i(p) = ∫_{ℝ^3} {\rm proj}_i(r) e^{-ip·r} dr``.
155157
We store ``\frac{1}{\sqrt Ω} \widehat{\rm proj}_i(k+G)`` in `proj_vectors`.
156158
"""
157159
function build_projection_vectors(basis::PlaneWaveBasis{T}, kpt::Kpoint,
158-
psps, psp_positions) where {T}
160+
psps::AbstractVector{<: NormConservingPsp},
161+
psp_positions) where {T}
159162
unit_cell_volume = basis.model.unit_cell_volume
160163
n_proj = count_n_proj(psps, psp_positions)
161164
n_G = length(G_vectors(basis, kpt))
162-
proj_vectors = zeros(Complex{T}, n_G, n_proj)
165+
proj_vectors = zeros(Complex{eltype(psp_positions[1][1])}, n_G, n_proj)
163166
G_plus_k = to_cpu(Gplusk_vectors(basis, kpt))
164167

165168
# Compute the columns of proj_vectors = 1/√Ω \hat proj_i(k+G)
@@ -230,3 +233,120 @@ function build_form_factors(psp, G_plus_k::AbstractVector{Vec3{TT}}) where {TT}
230233
end
231234
form_factors
232235
end
236+
237+
# Helpers for phonon computations.
238+
function build_projection_coefficients(basis::PlaneWaveBasis{T}, psp_groups) where {T}
239+
psps = [basis.model.atoms[first(group)].psp for group in psp_groups]
240+
psp_positions = [basis.model.positions[group] for group in psp_groups]
241+
build_projection_coefficients(T, psps, psp_positions)
242+
end
243+
function build_projection_vectors(basis::PlaneWaveBasis, kpt::Kpoint,
244+
psp_groups::AbstractVector{<: AbstractVector{<: Int}},
245+
positions)
246+
psps = [basis.model.atoms[first(group)].psp for group in psp_groups]
247+
psp_positions = [positions[group] for group in psp_groups]
248+
build_projection_vectors(basis, kpt, psps, psp_positions)
249+
end
250+
function PDPψk(basis, positions, psp_groups, kpt, kpt_minus_q, ψk)
251+
D = build_projection_coefficients(basis, psp_groups)
252+
P = build_projection_vectors(basis, kpt, psp_groups, positions)
253+
P_minus_q = build_projection_vectors(basis, kpt_minus_q, psp_groups, positions)
254+
P * (D * P_minus_q' * ψk)
255+
end
256+
257+
function compute_dynmat_δH(::TermAtomicNonlocal, basis::PlaneWaveBasis{T}, ψ, occupation,
258+
δψ, δoccupation, q) where {T}
259+
S = complex(T)
260+
model = basis.model
261+
psp_groups = [group for group in model.atom_groups
262+
if model.atoms[first(group)] isa ElementPsp]
263+
264+
# Early return if no pseudopotential atoms.
265+
isempty(psp_groups) && return nothing
266+
267+
δforces = [zero(Vec3{S}) for _ = 1:length(model.positions)]
268+
for group in psp_groups
269+
δψ_plus_q = transfer_blochwave_equivalent_to_actual(basis, δψ, q)
270+
for (ik, kpt) in enumerate(basis.kpoints)
271+
ψk = ψ[ik]
272+
δψk_plus_q = δψ_plus_q[ik].ψk
273+
kpt_plus_q = δψ_plus_q[ik].kpt
274+
275+
for idx in group
276+
δforces[idx] += map(1:3) do α
277+
δHψk = derivative_wrt_αs(model.positions, α, idx) do positions_αs
278+
PDPψk(basis, positions_αs, psp_groups, kpt_plus_q, kpt, ψ[ik])
279+
end
280+
δHψk_plus_q = derivative_wrt_αs(model.positions, α, idx) do positions_αs
281+
PDPψk(basis, positions_αs, psp_groups, kpt, kpt, ψ[ik])
282+
end
283+
-sum( 2occupation[ik][iband] * basis.kweights[ik]
284+
* dot(δψk_plus_q[:, iband], δHψk[:, iband])
285+
+ δoccupation[ik][iband] * basis.kweights[ik]
286+
* 2real(dot(ψk[:, iband], δHψk_plus_q[:, iband]))
287+
for iband=1:size(ψk, 2))
288+
end
289+
end
290+
end
291+
end
292+
293+
mpi_sum!(δforces, basis.comm_kpts)
294+
end
295+
296+
@views function compute_dynmat(term::TermAtomicNonlocal, basis::PlaneWaveBasis{T}, ψ,
297+
occupation; δψs, δoccupations, q=zero(Vec3{T}),
298+
kwargs...) where {T}
299+
S = complex(T)
300+
model = basis.model
301+
positions = model.positions
302+
n_atoms = length(positions)
303+
n_dim = model.n_dim
304+
305+
# Two contributions: dynmat_δH and dynmat_δ²H.
306+
307+
# dynmat_δH
308+
dynmat_δH = zeros(S, 3, n_atoms, 3, n_atoms)
309+
for s = 1:n_atoms, α = 1:n_dim
310+
dynmat_δH[:, :, α, s] .-= stack(
311+
compute_dynmat_δH(term, basis, ψ, occupation, δψs[α, s], δoccupations[α, s], q)
312+
)
313+
end
314+
315+
psp_groups = [group for group in model.atom_groups
316+
if model.atoms[first(group)] isa ElementPsp]
317+
# Early return if no pseudopotential atoms.
318+
isempty(psp_groups) && return dynmat_δH
319+
320+
# dynmat_δ²H
321+
dynmat_δ²H = zeros(S, 3, n_atoms, 3, n_atoms)
322+
δ²Hψ = zero.(ψ)
323+
for s = 1:n_atoms, α = 1:n_dim, β = 1:n_dim # zero if s ≠ t
324+
for (ik, kpt) in enumerate(basis.kpoints)
325+
δ²Hψ[ik] = derivative_wrt_αs(basis.model.positions, β, s) do positions_βs
326+
derivative_wrt_αs(positions_βs, α, s) do positions_βsαs
327+
PDPψk(basis, positions_βsαs, psp_groups, kpt, kpt, ψ[ik])
328+
end
329+
end
330+
dynmat_δ²H[β, s, α, s] += sum(occupation[ik][n] * basis.kweights[ik] *
331+
dot(ψ[ik][:, n], δ²Hψ[ik][:, n])
332+
for n=1:size(ψ[ik], 2))
333+
end
334+
end
335+
336+
dynmat_δH + dynmat_δ²H
337+
end
338+
339+
# δH is the Fourier transform perturbation of the nonlocal potential due to a position
340+
# displacement e^{iq·r} of the α coordinate of atom s.
341+
function compute_δHψ_αs(::TermAtomicNonlocal, basis::PlaneWaveBasis{T}, ψ, α, s, q) where {T}
342+
model = basis.model
343+
psp_groups = [group for group in model.atom_groups
344+
if model.atoms[first(group)] isa ElementPsp]
345+
346+
ψ_minus_q = transfer_blochwave_equivalent_to_actual(basis, ψ, -q)
347+
map(enumerate(basis.kpoints)) do (ik, kpt)
348+
derivative_wrt_αs(model.positions, α, s) do positions_αs
349+
PDPψk(basis, positions_αs, psp_groups, kpt, ψ_minus_q[ik].kpt, ψ_minus_q[ik].ψk)
350+
end
351+
end
352+
end

src/terms/xc.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,16 @@ end
362362

363363

364364
function apply_kernel(term::TermXc, basis::PlaneWaveBasis{T}, δρ::AbstractArray{Tδρ};
365-
ρ, kwargs...) where {T, Tδρ}
365+
ρ, q=zero(Vec3{T}), kwargs...) where {T, Tδρ}
366366
n_spin = basis.model.n_spin_components
367367
isempty(term.functionals) && return nothing
368368
@assert all(family(xc) in (:lda, :gga) for xc in term.functionals)
369369

370+
if !iszero(q) && !isnothing(term.ρcore)
371+
error("Phonon computations are not supported for models using nonlinear core \
372+
correction.")
373+
end
374+
370375
# Take derivatives of the density and the perturbation if needed.
371376
max_ρ_derivs = maximum(max_required_derivative, term.functionals)
372377
density = LibxcDensities(basis, max_ρ_derivs, ρ, nothing)

0 commit comments

Comments
 (0)