Skip to content

Commit 5a395c5

Browse files
committed
Add PspLinComb
1 parent 53da999 commit 5a395c5

File tree

7 files changed

+140
-10
lines changed

7 files changed

+140
-10
lines changed

src/DFTK.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export PspUpf
5252
include("pseudo/NormConservingPsp.jl")
5353
include("pseudo/PspHgh.jl")
5454
include("pseudo/PspUpf.jl")
55+
include("pseudo/PspLinComb.jl")
5556

5657
export ElementPsp
5758
export ElementCohenBergstresser
@@ -60,6 +61,7 @@ export ElementGaussian
6061
export charge_nuclear, charge_ionic
6162
export n_elec_valence, n_elec_core
6263
export element_symbol, mass, species # Note: Re-exported from AtomsBase
64+
export virtual_crystal_element
6365
include("elements.jl")
6466

6567
export SymOp

src/elements.jl

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ function core_charge_density_fourier(::Element, ::T)::T where {T <: Real}
5252
error("Abstract elements do not necesesarily provide core charge density.")
5353
end
5454

55-
# Fallback print function:
5655
Base.show(io::IO, el::Element) = print(io, "$(typeof(el))(:$(species(el)))")
5756

58-
5957
#
6058
# ElementCoulomb
6159
#
@@ -97,15 +95,19 @@ struct ElementPsp{P} <: Element
9795
family::Union{PseudoFamily,Nothing} # PseudoFamily if known, else Nothing
9896
mass # Atomic mass
9997
end
100-
function Base.show(io::IO, el::ElementPsp)
98+
function Base.show(io::IO, el::ElementPsp{P}) where {P}
10199
print(io, "ElementPsp(:$(el.species), ")
102100
if !isnothing(el.family)
103-
print(io, el.family, ")")
101+
print(io, el.family)
104102
elseif isempty(el.psp.identifier)
105-
print(io, "custom psp)")
103+
print(io, "custom psp")
106104
else
107-
print(io, "\"$(el.psp.identifier)\")")
105+
print(io, "\"$(el.psp.identifier)\"")
106+
end
107+
if P <: PspLinComb
108+
print(io, ", ", el.psp.description)
108109
end
110+
print(io, ")")
109111
end
110112
pseudofamily(el::ElementPsp) = el.family
111113

@@ -275,3 +277,28 @@ function local_potential_fourier(el::ElementGaussian, p::Real)
275277
-el.α * exp(- (p * el.L)^2 / 2) # = ∫_ℝ³ V(x) exp(-ix⋅p) dx
276278
end
277279
# TODO Strictly speaking needs a eval_psp_energy_correction
280+
281+
#
282+
# Helper functions
283+
#
284+
285+
# TODO Better name ?
286+
# TODO docstring
287+
function virtual_crystal_element(coefficients::Vector{<:Number}, elements::Vector{<:ElementPsp{<:NormConservingPsp}};
288+
species=ChemicalSpecies(0))
289+
length(coefficients) == length(elements) || throw(
290+
ArgumentError("Expect coefficients and elements to have equal length."))
291+
sum(coefficients) one(eltype(coefficients)) || throw(
292+
ArgumentError("Expect coefficients to sum to 1"))
293+
family = allequal(p -> p.family, elements) ? first(elements).family : nothing
294+
295+
description = "lin. comb. of "
296+
description *= join([(@sprintf "%.2f*%s" c "$(el.species)") for (c, el) in zip(coefficients, elements)], " ")
297+
lincomb_psp = PspLinComb(coefficients, [el.psp for el in elements]; description)
298+
lincomb_mass = sum(c * mass(el) for (c, el) in zip(coefficients, elements))
299+
ElementPsp(species, lincomb_psp, family, lincomb_mass)
300+
end
301+
function virtual_crystal_element::Number, αpsp::ElementPsp{<:NormConservingPsp},
302+
β::Number, βpsp::ElementPsp{<:NormConservingPsp}; kwargs...)
303+
virtual_crystal_element([α, β], [αpsp, βpsp]; kwargs...)
304+
end

src/pseudo/PspLinComb.jl

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Linear combination of two pseudos
2+
# Note that this data structure is deliberately not yet exported for now
3+
# as we may want to find a different solution for representing virtual crystal elements
4+
# in the future; users should use virtual_crystal_element to implicitly construct
5+
# these objects.
6+
#
7+
struct PspLinComb{T, P <: NormConservingPsp} <: NormConservingPsp
8+
lmax::Int
9+
h::Vector{Matrix{T}}
10+
hidx_to_psp_proj::Vector{Vector{Tuple{Int,Int}}} # map per angular momentum and projector index of this object to the tuple (psp index, projector index)
11+
coefficients::Vector{T}
12+
pseudos::Vector{P}
13+
identifier::String # String identifying the PSP
14+
description::String # Descriptive string
15+
end
16+
17+
function PspLinComb(coefficients::Vector{T}, pseudos::Vector{<:NormConservingPsp};
18+
description="psp linear combination") where {T}
19+
@assert length(coefficients) == length(pseudos)
20+
@assert sum(coefficients) one(eltype(coefficients))
21+
@assert !any(p -> p isa PspLinComb, pseudos)
22+
23+
# These assumptions we could lift, but we make tham to make our lifes easier
24+
@assert allequal(charge_ionic, pseudos)
25+
@assert allequal(has_valence_density, pseudos)
26+
@assert allequal(has_core_density, pseudos)
27+
@assert allequal(p -> p.lmax, pseudos)
28+
29+
TT = promote_type(T, eltype(eltype(first(pseudos).h)))
30+
lmax = first(pseudos).lmax
31+
h = Matrix{TT}[]
32+
hidx_to_psp_proj = Vector{Tuple{Int,Int}}[] # (ipsp, iproj)
33+
for l in 0:lmax
34+
proj_l_total = sum(p -> count_n_proj_radial(p, l), pseudos)
35+
hl = zeros(T, proj_l_total, proj_l_total)
36+
splits = Tuple{Int,Int}[] # (ipsp, iproj)
37+
38+
for (ipsp, p) in enumerate(pseudos)
39+
iproj_offset = isempty(splits) ? 0 : last(splits)[2]
40+
rnge = iproj_offset .+ (1:count_n_proj_radial(p, l))
41+
# TODO Is this the correct scaling of the coefficient in h ?
42+
hl[rnge, rnge] = p.h[l+1] * coefficients[ipsp]
43+
append!(splits, [(ipsp, iproj) for iproj in 1:count_n_proj_radial(p, l)])
44+
end
45+
push!(h, hl)
46+
push!(hidx_to_psp_proj, splits)
47+
end
48+
@assert length(h) == length(hidx_to_psp_proj) == lmax+1
49+
50+
identifier = ""
51+
PspLinComb(lmax, h, hidx_to_psp_proj, coefficients, pseudos, identifier, description)
52+
end
53+
54+
charge_ionic(psp::PspLinComb) = charge_ionic(first(psp.pseudos))
55+
has_valence_density(psp::PspLinComb) = all(has_valence_density, psp.pseudos)
56+
has_core_density(psp::PspLinComb) = any(has_core_density, psp.pseudos)
57+
58+
function eval_psp_projector_real(psp::PspLinComb, i, l, r::Real)
59+
(ipsp, iproj) = psp.hidx_to_psp_proj[l+1][i]
60+
eval_psp_projector_real(psp.pseudos[ipsp], iproj, l, r)
61+
end
62+
function eval_psp_projector_fourier(psp, i, l, p::Real)
63+
(ipsp, iproj) = psp.hidx_to_psp_proj[l+1][i]
64+
eval_psp_projector_fourier(psp.pseudos[ipsp], iproj, l, p)
65+
end
66+
67+
function eval_psp_energy_correction(T, psp::PspLinComb)
68+
sum(c * eval_psp_energy_correction(T, p) for (c, p) in zip(psp.coefficients, psp.pseudos))
69+
end
70+
71+
macro make_psplincomb_call(fn)
72+
quote
73+
function $fn(psp::PspLinComb, arg::Real)
74+
sum(c * $fn(pp, arg) for (c, pp) in zip(psp.coefficients, psp.pseudos))
75+
end
76+
end
77+
end
78+
@make_psplincomb_call DFTK.eval_psp_local_real
79+
@make_psplincomb_call DFTK.eval_psp_local_fourier
80+
@make_psplincomb_call DFTK.eval_psp_density_valence_real
81+
@make_psplincomb_call DFTK.eval_psp_density_valence_fourier
82+
@make_psplincomb_call DFTK.eval_psp_density_core_real
83+
@make_psplincomb_call DFTK.eval_psp_density_core_fourier

src/pseudo/pseudopotential_data.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Return the recommended kinetic energy cutoff, supersampling and density cutoff f
2929
Values may be `missing` if the data cannot be determined.
3030
"""
3131
function PseudoPotentialData.recommended_cutoff(el::Element)
32-
if isnothing(pseudofamily(el))
32+
if isnothing(pseudofamily(el)) || :X == element_symbol(el)
3333
return (; Ecut=missing, supersampling=missing, Ecut_density=missing)
3434
else
3535
return recommended_cutoff(pseudofamily(el), element_symbol(el))
@@ -45,7 +45,7 @@ Effectively this returns `pseudometa(pseudofamily(element), element_symbol(eleme
4545
"""
4646
function PseudoPotentialData.pseudometa(el::Element)
4747
family = pseudofamily(el)
48-
if isnothing(family)
48+
if isnothing(family) || :X == element_symbol(el)
4949
return Dict{String,Any}()
5050
else
5151
return pseudometa(family, element_symbol(el))

src/terms/operators.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
### Linear operators operating on quantities in real or fourier space
22
# This is the optimized low-level interface. Use the functions in Hamiltonian for high-level usage
33

4+
import Base: Matrix, Array
5+
46
"""
57
Linear operators that act on tuples (real, fourier)
68
The main entry point is `apply!(out, op, in)` which performs the operation `out += op*in`
@@ -41,7 +43,7 @@ struct NoopOperator{T <: Real} <: RealFourierOperator
4143
kpoint::Kpoint{T}
4244
end
4345
apply!(Hψ, op::NoopOperator, ψ) = nothing
44-
function Base.Matrix(op::NoopOperator)
46+
function Matrix(op::NoopOperator)
4547
n_Gk = length(G_vectors(op.basis, op.kpoint))
4648
zeros_like(G_vectors(op.basis), eltype(op.basis), n_Gk, n_Gk)
4749
end
@@ -60,7 +62,7 @@ end
6062
function apply!(Hψ, op::RealSpaceMultiplication, ψ)
6163
.real .+= op.potential .* ψ.real
6264
end
63-
function Base.Matrix(op::RealSpaceMultiplication)
65+
function Matrix(op::RealSpaceMultiplication)
6466
# V(G, G') = <eG|V|eG'> = 1/sqrt(Ω) <e_{G-G'}|V>
6567
pot_fourier = fft(op.basis, op.potential)
6668
n_G = length(G_vectors(op.basis, op.kpoint))

src/workarounds/forwarddiff_rules.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,16 @@ function construct_value(psp::PspUpf{T,I}) where {T <: AbstractFloat, I <: Abstr
202202
# but does not yet permit response derivatives w.r.t. UPF parameters.
203203
psp
204204
end
205+
construct_value(psp::PspLinComb) = psp
206+
function construct_value(psp::PspLinComb{<: Dual})
207+
PspLinComb(psp.lmax,
208+
[ForwardDiff.value.(hl) for hl in psp.h],
209+
psp.hidx_to_psp_proj,
210+
ForwardDiff.value.(psp.coefficients),
211+
construct_value.(psp.pseudos),
212+
psp.identifier,
213+
psp.description)
214+
end
205215

206216
function construct_value(basis::PlaneWaveBasis{T}) where {T <: Dual}
207217
# NOTE: This is a pretty slow function as it *recomputes* basically

test/PspLinComb.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
3+
# Test whether doing a 0.5 of one element with itself yields the identical answer as not doing the lincomb at all
4+
5+
6+
# Test whether the linear combination pseudo does not break any important properities (like normalisation, being norm-conserving etc.)

0 commit comments

Comments
 (0)