-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial exact exchange implementation #942
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Very basic setup, useful for testing | ||
using DFTK | ||
|
||
a = 10.26 # Silicon lattice constant in Bohr | ||
lattice = a / 2 * [[0 1 1.]; | ||
[1 0 1.]; | ||
[1 1 0.]] | ||
Si = ElementPsp(:Si; psp=load_psp("hgh/lda/Si-q4")) | ||
atoms = [Si, Si] | ||
positions = [ones(3)/8, -ones(3)/8] | ||
|
||
model = model_PBE(lattice, atoms, positions) | ||
basis = PlaneWaveBasis(model; Ecut=20, kgrid=[1, 1, 1]) | ||
scfres = self_consistent_field(basis; tol=1e-6) | ||
|
||
# Run hybrid-DFT tuning some DFTK defaults | ||
# (Anderson does not work well right now as orbitals not taken into account) | ||
model = model_PBE0(lattice, atoms, positions) | ||
basis = PlaneWaveBasis(model; basis.Ecut, basis.kgrid) | ||
scfres = self_consistent_field(basis; | ||
solver=DFTK.scf_damping_solver(1.0), | ||
tol=1e-8, scfres.ρ, scfres.ψ, | ||
diagtolalg=DFTK.AdaptiveDiagtol(; ratio_ρdiff=5e-4)) | ||
|
||
# Run Hartree-Fock | ||
model = model_HF(lattice, atoms, positions) | ||
basis = PlaneWaveBasis(model; basis.Ecut, basis.kgrid) | ||
scfres = self_consistent_field(basis; | ||
solver=DFTK.scf_damping_solver(1.0), | ||
tol=1e-8, scfres.ρ, scfres.ψ, | ||
diagtolalg=DFTK.AdaptiveDiagtol(; ratio_ρdiff=5e-4)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
@doc raw""" | ||
Exact exchange term: the Hartree-Exact exchange energy of the orbitals | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. calling it hartree is confusing? |
||
```math | ||
-1/2 ∑_{nm} f_n f_m ∫∫ \frac{ψ_n^*(r)ψ_m^*(r')ψ_n(r')ψ_m(r)}{|r - r'|} dr dr' | ||
``` | ||
""" | ||
struct ExactExchange | ||
scaling_factor::Real # to scale the term | ||
end | ||
ExactExchange(; scaling_factor=1) = ExactExchange(scaling_factor) | ||
(exchange::ExactExchange)(basis) = TermExactExchange(basis, exchange.scaling_factor) | ||
function Base.show(io::IO, exchange::ExactExchange) | ||
fac = isone(exchange.scaling_factor) ? "" : "scaling_factor=$(exchange.scaling_factor)" | ||
print(io, "ExactExchange($fac)") | ||
end | ||
struct TermExactExchange <: Term | ||
scaling_factor::Real # scaling factor, absorbed into poisson_green_coeffs | ||
poisson_green_coeffs::AbstractArray | ||
end | ||
function TermExactExchange(basis::PlaneWaveBasis{T}, scaling_factor) where T | ||
poisson_green_coeffs = 4T(π) ./ [sum(abs2, basis.model.recip_lattice * G) | ||
for G in to_cpu(G_vectors(basis))] | ||
poisson_green_coeffs[1] = 0 | ||
TermExactExchange(T(scaling_factor), scaling_factor .* poisson_green_coeffs) | ||
end | ||
|
||
# Note: Implementing exact exchange in a scalable and numerically stable way, such that it | ||
# rapidly converges with k-points is tricky. This implementation here is far too simple and | ||
# slow to be useful. | ||
# | ||
# ABINIT/src/66_wfs/m_getghc.F90 | ||
# ABINIT/src/66_wfs/m_fock_getghc.F90 | ||
# ABINIT/src/66_wfs/m_prep_kgb.F90 | ||
# ABINIT/src/66_wfs/m_bandfft_kpt.F90 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not give explicit references to code (as that might give the impression that we reproduced the implementation instead of a clean-room reimplementation) |
||
# | ||
# For further information (in particular on regularising the Coulomb), consider the following | ||
# https://www.vasp.at/wiki/index.php/Coulomb_singularity | ||
# https://journals.aps.org/prb/pdf/10.1103/PhysRevB.34.4405 (QE default) | ||
# https://journals.aps.org/prb/pdf/10.1103/PhysRevB.73.205119 | ||
# https://journals.aps.org/prb/pdf/10.1103/PhysRevB.77.193110 | ||
# https://docs.abinit.org/topics/documents/hybrids-2017.pdf (Abinit apparently | ||
# uses a short-ranged Coulomb) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that we should meet and think about, also with @ELallinec who is thinking about the Coulomb singularity There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely as a first attempt it's reasonable to have a short-ranged Coulomb. |
||
|
||
@timing "ene_ops: ExactExchange" function ene_ops(term::TermExactExchange, | ||
basis::PlaneWaveBasis{T}, ψ, occupation; | ||
kwargs...) where {T} | ||
if isnothing(ψ) || isnothing(occupation) | ||
return (; E=T(0), ops=NoopOperator.(basis, basis.kpoints)) | ||
end | ||
|
||
# TODO Occupation threshold | ||
ψ, occupation = select_occupied_orbitals(basis, ψ, occupation; threshold=1e-8) | ||
|
||
E = zero(T) | ||
|
||
@assert length(ψ) == 1 # TODO: make it work for more kpoints | ||
ik = 1 | ||
kpt = basis.kpoints[ik] | ||
occk = occupation[ik] | ||
ψk = ψ[ik] | ||
|
||
for (n, ψn) in enumerate(eachcol(ψk)) | ||
ψn_real = ifft(basis, kpt, ψn) | ||
for (m, ψm) in enumerate(eachcol(ψk)) | ||
ψm_real = ifft(basis, kpt, ψm) | ||
ρnm_real = conj(ψn_real) .* ψm_real | ||
ρnm_fourier = fft(basis, ρnm_real) | ||
|
||
fac_mn = occk[n] * occk[m] / T(2) | ||
E -= fac_mn * real(dot(ρnm_fourier .* term.poisson_green_coeffs, ρnm_fourier)) | ||
end | ||
end | ||
|
||
ops = [ExchangeOperator(basis, kpt, term.poisson_green_coeffs, occk, ψk)] | ||
(; E, ops) | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
@testitem "Helium exchange energy" setup=[TestCases] begin | ||
using DFTK | ||
using LinearAlgebra | ||
silicon = TestCases.silicon | ||
|
||
lattice = 10diagm(ones(3)) | ||
positions = [0.5ones(3)] | ||
atoms = [ElementCoulomb(:He)] | ||
model = model_atomic(lattice, atoms, positions) | ||
basis = PlaneWaveBasis(model; Ecut=15, kgrid=(1, 1, 1)) | ||
scfres = self_consistent_field(basis) | ||
|
||
Eh, oph = DFTK.ene_ops(Hartree()(basis), basis, scfres.ψ, scfres.occupation; scfres...) | ||
Ex, opx = DFTK.ene_ops(ExactExchange()(basis), basis, scfres.ψ, scfres.occupation; scfres...) | ||
@test abs(Ex + Eh) < 1e-12 | ||
|
||
mat_h = real.(Matrix(only(oph))) | ||
mat_x = real.(Matrix(only(opx))) | ||
@show maximum(abs, mat_x - mat_x') | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
put that check in the instantiation of the exchange term?