Skip to content

[WIP] Hybrid functionals #717

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

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4b9a614
Overhaul docs organisation (#684)
antoine-levitt Jul 25, 2022
69f534c
minor corrections in docs
antoine-levitt Jul 25, 2022
5081905
minor doc fix
antoine-levitt Jul 25, 2022
30fe9ea
Fix dual handling for parametrised functionals (#699)
mfherbst Jul 29, 2022
a3bc617
Typo in dos.jl (#702)
Aug 2, 2022
f28b9ec
fix spglib (#707)
mfherbst Aug 16, 2022
0684562
Bump version: 0.5.5 → 0.5.6
mfherbst Aug 16, 2022
06dbf3b
Add documentation about conducting a convergence study (#701)
killah-t-cell Aug 26, 2022
40fb53d
Fix some image links in documentation (#713)
mfherbst Aug 27, 2022
222ae6a
Bump version: 0.5.6 → 0.5.7
mfherbst Aug 29, 2022
77ef797
typo
antoine-levitt Aug 30, 2022
ce348a9
ForwardDiff: Return dual energies in scfres (#709)
niklasschmitz Aug 31, 2022
8279d37
Remove advertisement for summer school (#716)
mfherbst Sep 1, 2022
582d3ee
Merge remote-tracking branch 'upstream/master'
BKaperick Sep 6, 2022
4603777
Add fock_exchange term and energy for one kpoint
BKaperick Sep 1, 2022
ddf11cf
Add ExchangeOperator to fock_exchange with consistency test
BKaperick Sep 1, 2022
bf0e14b
WIP connecting to LibXc
BKaperick Sep 6, 2022
5627ec1
Copy-paste gga methods in DispatchFunctional for hyb_gga
BKaperick Sep 6, 2022
3426b5e
Add scaling_factor to fock_exchange term
BKaperick Sep 6, 2022
d008d40
Fix printing bug in other terms using scale_factor
BKaperick Sep 6, 2022
ada9b29
Reduce code duplication (with Union) and implement hybrid lda and mgga
BKaperick Sep 10, 2022
f770fde
Compute green coeffs only once, and set first term to 0
BKaperick Sep 10, 2022
b3d650c
Michael comments part 1
BKaperick Sep 23, 2022
cb791f9
Merge remote-tracking branch 'upstream/master'
BKaperick Mar 15, 2023
2ead1c5
Michael comments part 2
BKaperick Mar 15, 2023
81854cc
Clean term implementation
BKaperick Mar 15, 2023
b1a703d
Clean operator
BKaperick Mar 15, 2023
c69a914
Merge branch 'master' into hybrid_functionals
BKaperick Mar 18, 2023
91a5dda
Some progress
mfherbst Jun 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/CompatHelper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:

jobs:
CompatHelper:
if: github.repository_owner = "JuliaMolSim"
runs-on: ubuntu-latest
steps:
- uses: julia-actions/setup-julia@latest
Expand Down
3 changes: 2 additions & 1 deletion src/DFTK.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export Hamiltonian
export HamiltonianBlock
export energy_hamiltonian
export Kinetic
export ExactExchange
export ExternalFromFourier
export ExternalFromReal
export AtomicLocal
Expand Down Expand Up @@ -132,7 +133,7 @@ include("eigen/preconditioners.jl")
include("eigen/diag.jl")

export model_atomic
export model_DFT, model_PBE, model_LDA, model_SCAN
export model_DFT, model_PBE, model_LDA, model_SCAN, model_PBE0, model_HF
include("standard_models.jl")

export KerkerMixing, KerkerDosMixing, SimpleMixing, DielectricMixing
Expand Down
20 changes: 17 additions & 3 deletions src/DispatchFunctional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ function LibxcFunctional(identifier::Symbol)
@assert fun.kind in (:exchange, :correlation, :exchange_correlation)
kind = Dict(:exchange => :x, :correlation => :c, :exchange_correlation => :xc)[fun.kind]

@assert fun.family in (:lda, :gga, :mgga) # Hybrids not supported yet.
if fun.family == :mgga && Libxc.needs_laplacian(fun)
family = :mggal
@assert fun.family in (:lda, :gga, :mgga, :hyb_lda, :hyb_gga, :hyb_mgga)

# Libxc maintains the distinction between hybrid and non-hybrid equivalents,
# but this distinction is not relevant for the functional interface
if startswith(string(fun.family), "hyb")
family = Symbol(string(fun.family)[5:end])
else
family = fun.family
end

if family == :mgga && Libxc.needs_laplacian(fun)
family = :mggal
end
LibxcFunctional{family,kind}(identifier)
end

Expand Down Expand Up @@ -154,3 +161,10 @@ for fun in (:potential_terms, :kernel_terms)
end
end
end

# TODO This is hackish for now until Libxc has fully picked up the DftFunctionals.jl interface
exx_coefficient(::Functional{:lda}) = nothing
exx_coefficient(::Functional{:gga}) = nothing
exx_coefficient(::Functional{:mgga}) = nothing
exx_coefficient(fun::DispatchFunctional) = exx_coefficient(fun.inner)
exx_coefficient(fun::LibxcFunctional) = Libxc.Functional(fun.identifier).exx_coefficient
2 changes: 1 addition & 1 deletion src/fft.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function ifft!(f_real::AbstractArray3, basis::PlaneWaveBasis, f_fourier::Abstrac
f_real .*= basis.ifft_normalization
end
function ifft!(f_real::AbstractArray3, basis::PlaneWaveBasis,
kpt::Kpoint, f_fourier::AbstractVector; normalize=true)
kpt::Kpoint, f_fourier::AbstractVector; normalize=true)
@assert length(f_fourier) == length(kpt.mapping)
@assert size(f_real) == basis.fft_size

Expand Down
34 changes: 32 additions & 2 deletions src/standard_models.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ function model_DFT(lattice::AbstractMatrix,
xc::Xc;
extra_terms=[], kwargs...)
model_name = isempty(xc.functionals) ? "rHF" : join(string.(xc.functionals), "+")
exx = [ExactExchange(; scaling_factor=exx_coefficient(f))
for f in xc.functionals if !isnothing(exx_coefficient(f))]
if !isempty(exx)
@assert length(exx) == 1
@warn "Exact exchange in DFTK is hardly optimised and not yet production-ready."
end

model_atomic(lattice, atoms, positions;
extra_terms=[Hartree(), xc, extra_terms...], model_name, kwargs...)
extra_terms=[Hartree(), xc, exx..., extra_terms...], model_name, kwargs...)
end
function model_DFT(lattice::AbstractMatrix,
atoms::Vector{<:Element},
Expand Down Expand Up @@ -75,8 +82,31 @@ function model_SCAN(lattice::AbstractMatrix, atoms::Vector{<:Element},
end


"""
Build an PBE0 model from the specified atoms.
DOI:10.1103/PhysRevLett.77.3865
"""
function model_PBE0(lattice::AbstractMatrix, atoms::Vector{<:Element},
positions::Vector{<:AbstractVector}; kwargs...)
model_DFT(lattice, atoms, positions, [:hyb_gga_xc_pbeh]; kwargs...)
end


"""
Build an Hartree-Fock model from the specified atoms.
"""
function model_HF(lattice::AbstractMatrix, atoms::Vector{<:Element},
positions::Vector{<:AbstractVector}; extra_terms=[], kwargs...)
@warn "Exact exchange in DFTK is hardly optimised and not yet production-ready."
model_atomic(lattice, atoms, positions;
extra_terms=[Hartree(), ExactExchange(), extra_terms...],
model_name="HF", kwargs...)
end


# Generate equivalent functions for AtomsBase
for fun in (:model_atomic, :model_DFT, :model_LDA, :model_PBE, :model_SCAN)
for fun in (:model_atomic, :model_DFT, :model_LDA, :model_PBE, :model_SCAN,
:model_PBE0, :model_HF)
@eval function $fun(system::AbstractSystem, args...; kwargs...)
@assert !(:magnetic_moments in keys(kwargs))
parsed = parse_system(system)
Expand Down
4 changes: 2 additions & 2 deletions src/terms/Hamiltonian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ function Base.size(block::HamiltonianBlock)
end

import Base: Matrix, Array
Array(block::HamiltonianBlock) = Matrix(block)
Matrix(block::HamiltonianBlock) = sum(Matrix, block.operators)
Array(block::HamiltonianBlock) = Matrix(block)
Matrix(block::HamiltonianBlock) = sum(Matrix, block.operators)
Matrix(block::GenericHamiltonianBlock) = sum(Matrix, block.optimized_operators)

struct Hamiltonian
Expand Down
83 changes: 83 additions & 0 deletions src/terms/exact_exchange.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Exact exchange term: the Hartree-Exact exchange energy of the orbitals

-1/2 ∑ ∫∫ ϕ_i^*(r)ϕ_j^*(r')ϕ_i(r')ϕ_j(r) / |r - r'| dr dr'

"""
struct ExactExchange
scaling_factor::Real # to scale the term (e.g. for hybrid models)
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
scale = T(scaling_factor)

# q = T[0.5, 0.5, 0.5]
# Gqs = map(G_vectors(basis)) do G
# recip_vector_red_to_cart(basis.model, G + q)
# end
# poisson_green_coeffs = 4T(π) * scale ./ norm2.(Gqs)

poisson_green_coeffs = 4T(π) * scale ./ norm2.(G_vectors_cart(basis))
poisson_green_coeffs[1] = 0 # Compensating charge background => Zero DC

@assert iszero(G_vectors(basis, basis.kpoints[1])[1])

TermExactExchange(scale, 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.
#
# 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://docs.abinit.org/topics/documents/hybrids-2017.pdf (Abinit apparently
# uses 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

@assert iszero(basis.model.temperature) # ground state
ψ, occupation = select_occupied_orbitals(basis, ψ, occupation; threshold=0.1)
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))
for (m, ψm) in enumerate(eachcol(ψk))
ψn_real = ifft(basis, kpt, ψn)
ψm_real = ifft(basis, kpt, ψm)

ρnm_real = conj(ψn_real) .* ψm_real
ρmn_real = conj(ψm_real) .* ψn_real

Vx_nm_four = fft(basis, ρnm_real) .* term.poisson_green_coeffs
Vx_nm_real = ifft(basis, Vx_nm_four)

occ_mn = occk[n] * occk[m]
E -= real(dot(Vx_nm_real, ρmn_real)) * basis.dvol * occ_mn / 2
end
end

ops = [ExchangeOperator(basis, kpt, term.poisson_green_coeffs, occk, ψk)]
(E=E, ops=ops)
end
2 changes: 1 addition & 1 deletion src/terms/hartree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ end
Hartree(; scaling_factor=1) = Hartree(scaling_factor)
(hartree::Hartree)(basis) = TermHartree(basis, hartree.scaling_factor)
function Base.show(io::IO, hartree::Hartree)
fac = isone(hartree.scaling_factor) ? "" : ", scaling_factor=$scaling_factor"
fac = isone(hartree.scaling_factor) ? "" : ", scaling_factor=$(hartree.scaling_factor)"
print(io, "Hartree($fac)")
end

Expand Down
40 changes: 40 additions & 0 deletions src/terms/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ They also implement mul! and Matrix(op) for exploratory use.
"""
abstract type RealFourierOperator end
# RealFourierOperator contain fields `basis` and `kpoint`
#
Array(op::RealFourierOperator) = Matrix(op)

# Slow fallback for getting operator as a dense matrix
function Matrix(op::RealFourierOperator)
T = complex(eltype(op.basis))
n_G = length(G_vectors(op.basis, op.kpoint))
H = zeros(T, n_G, n_G)
ψ = zeros(T, n_G)
@views for i in 1:n_G
ψ[i] = one(T)
mul!(H[:, i], op, ψ)
ψ[i] = zero(T)
end
H
end

# Unoptimized fallback, intended for exploratory use only.
# For performance, call through Hamiltonian which saves FFTs.
Expand Down Expand Up @@ -162,3 +178,27 @@ function optimize_operators_(ops)
sum([op.potential for op in RSmults]))
[nonRSmults..., combined_RSmults]
end

struct ExchangeOperator{T <: Real} <: RealFourierOperator
basis::PlaneWaveBasis{T}
kpoint::Kpoint{T}
poisson_green_coeffs # TODO This needs to be typed
occk # TODO This needs to be typed
ψk # TODO This needs to be typed
end
function apply!(Hψ, op::ExchangeOperator, ψ)
# Hψ = - ∑_n f_n ψ_n(r) ∫ (ψ_n)†(r') * ψ(r') / |r-r'|dr'
for (n, ψnk) in enumerate(eachcol(op.ψk))
ψnk_real = ifft(op.basis, op.kpoint, ψnk)
x_real = conj(ψnk_real) .* ψ.real

# Compute integral by Poisson solve
x_four = fft(op.basis, x_real)
Vx_four = x_four .* op.poisson_green_coeffs
Vx_real = ifft(op.basis, Vx_four)

# Real-space multiply and accumulate
Hψ.real .-= op.occk[n] .* ψnk_real .* Vx_real
end
end
# TODO Implement Matrix(op::ExchangeOperator)
1 change: 1 addition & 0 deletions src/terms/terms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ include("ewald.jl")
include("psp_correction.jl")
include("entropy.jl")
include("pairwise.jl")
include("exact_exchange.jl")

include("magnetic.jl")
breaks_symmetries(::Magnetic) = true
Expand Down
Loading