Skip to content

Commit 90f6ecf

Browse files
committed
Add PspLinComb
1 parent 09340cf commit 90f6ecf

File tree

6 files changed

+117
-6
lines changed

6 files changed

+117
-6
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: 22 additions & 2 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
#
@@ -275,3 +273,25 @@ function local_potential_fourier(el::ElementGaussian, p::Real)
275273
-el.α * exp(- (p * el.L)^2 / 2) # = ∫_ℝ³ V(x) exp(-ix⋅p) dx
276274
end
277275
# TODO Strictly speaking needs a eval_psp_energy_correction
276+
277+
#
278+
# Helper functions
279+
#
280+
281+
# TODO Better name ?
282+
# TODO docstring
283+
function virtual_crystal_element(coefficients::Vector{<:Number}, elements::Vector{<:ElementPsp{<:NormConservingPsp}};
284+
species=ChemicalSpecies(0))
285+
length(coefficients) == length(elements) || throw(
286+
ArgumentError("Expect coefficients and elements to have equal length."))
287+
sum(coefficients) one(eltype(coefficients)) || throw(
288+
ArgumentError("Expect coefficients to sum to 1"))
289+
family = allequal(p -> p.family, elements) ? first(elements).family : nothing
290+
lincomb_psp = PspLinComb(coefficients, [el.psp for el in elements])
291+
lincomb_mass = sum(c * mass(el) for (c, el) in zip(coefficients, elements))
292+
ElementPsp(species, lincomb_psp, family, lincomb_mass)
293+
end
294+
function virtual_crystal_element::Number, αpsp::ElementPsp{<:NormConservingPsp},
295+
β::Number, βpsp::ElementPsp{<:NormConservingPsp}; kwargs...)
296+
virtual_crystal_element([α, β], [αpsp, βpsp]; kwargs...)
297+
end

src/pseudo/PspLinComb.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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}) where {T}
18+
@assert length(coefficients) == length(pseudos)
19+
@assert sum(coefficients) one(eltype(coefficients))
20+
21+
# These assumptions we could lift, but we make tham to make our lifes easier
22+
@assert allequal(charge_ionic, pseudos)
23+
@assert allequal(has_valence_density, pseudos)
24+
@assert allequal(has_core_density, pseudos)
25+
@assert allequal(p -> p.lmax, pseudos)
26+
27+
TT = promote_type(T, eltype(eltype(first(pseudos).h)))
28+
lmax = first(pseudos).lmax
29+
h = Matrix{TT}[]
30+
hidx_to_psp_proj = Vector{Tuple{Int,Int}}[] # (ipsp, iproj)
31+
for l in 0:lmax
32+
proj_l_total = sum(p -> count_n_proj_radial(p, l), pseudos)
33+
hl = zeros(T, proj_l_total, proj_l_total)
34+
splits = Tuple{Int,Int}[] # (ipsp, iproj)
35+
36+
for (ipsp, p) in enumerate(pseudos)
37+
iproj_offset = isempty(splits) ? 0 : last(splits)[2]
38+
rnge = iproj_offset .+ (1:count_n_proj_radial(p, l))
39+
hl[rnge, rnge] = p.h[l+1] * coefficients[ipsp] # TODO Correct scaling of coefficient ?
40+
append!(splits, [(ipsp, iproj) for iproj in 1:count_n_proj_radial(p, l)])
41+
end
42+
push!(h, hl)
43+
push!(hidx_to_psp_proj, splits)
44+
end
45+
@assert length(h) == lmax+1
46+
@assert length(hidx_to_psp_proj) == lmax+1
47+
48+
identifier = ""
49+
description = "Lin. Comb. of $(join([p.identifier for p in pseudos], " "))"
50+
PspLinComb(lmax, h, hidx_to_psp_proj, coefficients, pseudos, identifier, description)
51+
end
52+
53+
function eval_psp_projector_real(psp::PspLinComb, i, l, r::Real)
54+
(ipsp, iproj) = psp.hidx_to_psp_proj[l+1][i]
55+
eval_psp_projector_real(psp.pseudos[ipsp], iproj, l, r)
56+
end
57+
function eval_psp_projector_fourier(psp, i, l, p::Real)
58+
(ipsp, iproj) = psp.hidx_to_psp_proj[l+1][i]
59+
eval_psp_projector_fourier(psp.pseudos[ipsp], iproj, l, p)
60+
end
61+
62+
function charge_ionic(psp::PspLinComb)::Int
63+
sum(c * charge_ionic(p) for (c, p) in zip(psp.coefficients, psp.pseudos))
64+
end
65+
function eval_psp_energy_correction(T, psp::PspLinComb)
66+
sum(c * eval_psp_energy_correction(T, p) for (c, p) in zip(psp.coefficients, psp.pseudos))
67+
end
68+
69+
has_valence_density(psp::PspLinComb) = all(has_valence_density, psp.pseudos)
70+
has_core_density(psp::PspLinComb) = any(has_core_density, psp.pseudos)
71+
72+
macro make_psplincomb_call(fn)
73+
quote
74+
function $fn(psp::PspLinComb, arg::Real)
75+
sum(c * $fn(pp, arg) for (c, pp) in zip(psp.coefficients, psp.pseudos))
76+
end
77+
end
78+
end
79+
@make_psplincomb_call DFTK.eval_psp_local_real
80+
@make_psplincomb_call DFTK.eval_psp_local_fourier
81+
@make_psplincomb_call DFTK.eval_psp_density_valence_real
82+
@make_psplincomb_call DFTK.eval_psp_density_valence_fourier
83+
@make_psplincomb_call DFTK.eval_psp_density_core_real
84+
@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))

test/PspLinComb.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
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

0 commit comments

Comments
 (0)