Skip to content
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

Reupload the projected density of states #1002

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions examples/dos.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# # Densities of states (DOS)
# In this example, we'll plot the DOS, local DOS, and projected DOS of Silicon.
# DOS computation only supports finite temperature.
# Projected DOS only supports PspUpf.

using DFTK
using Unitful
using Plots
using LazyArtifacts

## Define the geometry and pseudopotential
a = 10.26 # Silicon lattice constant in Bohr
lattice = a / 2 * [[0 1 1.0];
[1 0 1.0];
[1 1 0.0]]
Si = ElementPsp(:Si; psp=load_psp(artifact"pd_nc_sr_lda_standard_0.4.1_upf", "Si.upf"))
atoms = [Si, Si]
positions = [ones(3) / 8, -ones(3) / 8]

## Run SCF
model = model_LDA(lattice, atoms, positions; temperature=5e-3)
basis = PlaneWaveBasis(model; Ecut=15, kgrid=[4, 4, 4], symmetries_respect_rgrid=true)
scfres = self_consistent_field(basis, tol=1e-8)

## Plot the DOS
plot_dos(scfres)

## Plot the local DOS along one direction
plot_ldos(scfres; n_points=100, ldos_xyz=[:, 10, 10])

## Plot the projected DOS
plot_pdos(scfres; εrange=(-0.3, 0.5))
96 changes: 95 additions & 1 deletion ext/DFTKPlotsExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Brillouin: KPath
using DFTK
using DFTK: is_metal, data_for_plotting, spin_components, default_band_εrange
import DFTK: plot_dos, plot_bandstructure
import DFTK: plot_dos, plot_bandstructure, plot_ldos, plot_pdos
using Plots
using Unitful
using UnitfulAtomic
Expand Down Expand Up @@ -108,4 +108,98 @@
end
plot_dos(scfres; kwargs...) = plot_dos(scfres.basis, scfres.eigenvalues; scfres.εF, kwargs...)

function plot_ldos(basis, eigenvalues, ψ; εF=nothing, unit=u"hartree",

Check warning on line 111 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L111

Added line #L111 was not covered by tests
temperature=basis.model.temperature,
smearing=basis.model.smearing,
εrange=default_band_εrange(eigenvalues; εF),
n_points=1000, ldos_xyz=[:, 1, 1], kwargs...)
eshift = something(εF, 0.0)
εs = range(austrip.(εrange)..., length=n_points)

Check warning on line 117 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L116-L117

Added lines #L116 - L117 were not covered by tests

# Constant to convert from AU to the desired unit
to_unit = ustrip(auconvert(unit, 1.0))

Check warning on line 120 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L120

Added line #L120 was not covered by tests

# LDε has three dimensions (x, y, z)
# map on a single axis to plot the variation with εs
LDεs = dropdims.(compute_ldos.(εs, Ref(basis), Ref(eigenvalues), Ref(ψ); smearing, temperature); dims=4)
LDεs_slice = similar(LDεs[1], n_points, length(LDεs[1][ldos_xyz...]))
for (i, LDε) in enumerate(LDεs)
LDεs_slice[i, :] = LDε[ldos_xyz...]
end
p = heatmap(1:size(LDεs_slice, 2), (εs .- eshift) .* to_unit, LDεs_slice; kwargs...)
if !isnothing(εF)
Plots.hline!(p, [0.0], label="εF", color=:green, lw=1.5)

Check warning on line 131 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L124-L131

Added lines #L124 - L131 were not covered by tests
end

if isnothing(εF)
Plots.ylabel!(p, "eigenvalues ($(string(unit)))")

Check warning on line 135 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L134-L135

Added lines #L134 - L135 were not covered by tests
else
Plots.ylabel!(p, "eigenvalues -ε_F ($(string(unit)))")

Check warning on line 137 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L137

Added line #L137 was not covered by tests
end
p

Check warning on line 139 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L139

Added line #L139 was not covered by tests
end
plot_ldos(scfres; kwargs...) = plot_ldos(scfres.basis, scfres.eigenvalues, scfres.ψ; scfres.εF, kwargs...)

Check warning on line 141 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L141

Added line #L141 was not covered by tests

function plot_pdos(basis, eigenvalues, ψ, psp_group;

Check warning on line 143 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L143

Added line #L143 was not covered by tests
εF=nothing, unit=u"hartree",
temperature=basis.model.temperature,
smearing=basis.model.smearing,
εrange=default_band_εrange(eigenvalues; εF),
n_points=1000, p=nothing, kwargs...)
eshift = something(εF, 0.0)
εs = range(austrip.(εrange)..., length=n_points)

Check warning on line 150 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L149-L150

Added lines #L149 - L150 were not covered by tests

# Constant to convert from AU to the desired unit
to_unit = ustrip(auconvert(unit, 1.0))

Check warning on line 153 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L153

Added line #L153 was not covered by tests

# Calculate the projections of the first atom in the atom group
pdos = compute_pdos(εs, basis, eigenvalues, ψ, psp_group;

Check warning on line 156 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L156

Added line #L156 was not covered by tests
temperature, smearing)

# Summing all angular components for each atom orbital
psp = basis.model.atoms[first(psp_group)].psp
n_pswfc = DFTK.count_n_pswfc_radial(psp)
pdos_label = Vector{String}(undef, n_pswfc)
pdos_orbital = Vector{Vector{eltype(pdos)}}(undef, n_pswfc)
count = 1
for l = 0:psp.lmax
il_n = DFTK.count_n_pswfc_radial(psp, l)
mfet_count = sum(x -> DFTK.count_n_pswfc(psp,x), 0:l-1; init=1)
for il = 1:il_n
mfet = mfet_count .+ il_n .* (collect(1:2l+1) .- 1)
pdos_label[count] = psp.pswfc_labels[l+1][il]
pdos_orbital[count] = dropdims(sum(pdos[:, mfet], dims=2); dims=2)
count += 1
mfet_count += 1
end
end
pdos_label = (String(basis.model.atoms[first(psp_group)].symbol) * "-") .* pdos_label

Check warning on line 176 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L160-L176

Added lines #L160 - L176 were not covered by tests

# Plot pdos for each orbital
p = something(p, Plots.plot(; kwargs...))
for i = 1:n_pswfc
Plots.plot!(p, (εs .- eshift) .* to_unit, pdos_orbital[i];

Check warning on line 181 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L179-L181

Added lines #L179 - L181 were not covered by tests
label=pdos_label[i])
end
p

Check warning on line 184 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L183-L184

Added lines #L183 - L184 were not covered by tests
end

function plot_pdos(scfres; kwargs...)

Check warning on line 187 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L187

Added line #L187 was not covered by tests
# Plot DOS
p = plot_dos(scfres; scfres.εF, kwargs...)

Check warning on line 189 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L189

Added line #L189 was not covered by tests

# TODO Require symmetrization with respect to kpoints and BZ symmetry
# (now achieved by unfolding all the quantities).
scfres_unfold = DFTK.unfold_bz(scfres)
psp_groups = [group for group in scfres_unfold.basis.model.atom_groups

Check warning on line 194 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L193-L194

Added lines #L193 - L194 were not covered by tests
if scfres_unfold.basis.model.atoms[first(group)] isa ElementPsp]

# Plot PDOS
for group in psp_groups
plot_pdos(scfres_unfold.basis, scfres_unfold.eigenvalues,

Check warning on line 199 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L198-L199

Added lines #L198 - L199 were not covered by tests
scfres_unfold.ψ, group; scfres.εF, p, kwargs...)
end
p

Check warning on line 202 in ext/DFTKPlotsExt.jl

View check run for this annotation

Codecov / codecov/patch

ext/DFTKPlotsExt.jl#L201-L202

Added lines #L201 - L202 were not covered by tests
end

end
3 changes: 3 additions & 0 deletions src/DFTK.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ export compute_stresses_cart
include("postprocess/stresses.jl")
export compute_dos
export compute_ldos
export compute_pdos
export plot_dos
export plot_ldos
export plot_pdos
include("postprocess/dos.jl")
export compute_χ0
export apply_χ0
Expand Down
75 changes: 75 additions & 0 deletions src/postprocess/dos.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
#
# LDOS (local density of states)
# LD(ε) = sum_n f_n' |ψn|^2 = sum_n δ(ε - ε_n) |ψn|^2
#
# PDOS (projected density of states)
# PD(ε) = sum_n f_n' |<ψn,ϕlmi>|^2
# ϕlmi = ∑_R ϕlmi(r-pos-R) is a periodic atomic wavefunction centered at pos

"""
Total density of states at energy ε
Expand Down Expand Up @@ -63,7 +67,78 @@
compute_ldos(ε, scfres.basis, scfres.eigenvalues, scfres.ψ; kwargs...)
end

"""
Projected density of states at energy ε for the first atom in the atom group.
"""
function compute_pdos(ε, basis, eigenvalues, ψ, psp_group;

Check warning on line 73 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L73

Added line #L73 was not covered by tests
smearing=basis.model.smearing,
temperature=basis.model.temperature)
if (temperature == 0) || smearing isa Smearing.None
error("compute_pdos only supports finite temperature")

Check warning on line 77 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L76-L77

Added lines #L76 - L77 were not covered by tests
end
filled_occ = filled_occupation(basis.model)

Check warning on line 79 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L79

Added line #L79 was not covered by tests

# Calculate the projections of the first atom in the atom group
psp = basis.model.atoms[first(psp_group)].psp
position = basis.model.positions[first(psp_group)]
projs = compute_pdos_projs(basis, eigenvalues, ψ, psp, position)

Check warning on line 84 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L82-L84

Added lines #L82 - L84 were not covered by tests

D = zeros(typeof(ε[1]), length(ε), size(projs[1], 2))
for (i, iε) in enumerate(ε)
for (ik, projk) in enumerate(projs)
@views for (iband, εnk) in enumerate(eigenvalues[ik])
enred = (εnk - iε) / temperature
D[i, :] .-= (filled_occ .* basis.kweights[ik] .* projk[iband, :]

Check warning on line 91 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L86-L91

Added lines #L86 - L91 were not covered by tests
./ temperature
.* Smearing.occupation_derivative(smearing, enred))
end
end
end
D = mpi_sum(D, basis.comm_kpts)

Check warning on line 97 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L94-L97

Added lines #L94 - L97 were not covered by tests
end

function compute_pdos(scfres::NamedTuple; ε=scfres.εF, kwargs...)

Check warning on line 100 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L100

Added line #L100 was not covered by tests
# TODO Require symmetrization with respect to kpoints and BZ symmetry
# (now achieved by unfolding all the quantities).
scfres = unfold_bz(scfres)
psp_groups = [group for group in scfres.basis.model.atom_groups

Check warning on line 104 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L103-L104

Added lines #L103 - L104 were not covered by tests
if scfres.basis.model.atoms[first(group)] isa ElementPsp]
[compute_pdos(ε, scfres.basis, scfres.eigenvalues, scfres.ψ, group; kwargs...)

Check warning on line 106 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L106

Added line #L106 was not covered by tests
for group in psp_groups]
end

# Build projection matrix for a single atom
# Stored as projs[K][nk,lmi] = |<ψnk, f_lmi>|^2,
# where K is running over all k-points, l, m are AM quantum numbers,
# and i is running over all pseudo-atomic wavefunctions for a given l.
function compute_pdos_projs(basis, eigenvalues, ψ, psp::PspUpf, position)
position = vector_red_to_cart(basis.model, position)
G_plus_k_all = [to_cpu(Gplusk_vectors_cart(basis, basis.kpoints[ik]))

Check warning on line 116 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L114-L116

Added lines #L114 - L116 were not covered by tests
for ik = 1:length(basis.kpoints)]

# Build Fourier transform factors of pseudo-wavefunctions centered at 0.
fourier_form = atomic_centered_function_form_factors(psp, eval_psp_pswfc_fourier,

Check warning on line 120 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L120

Added line #L120 was not covered by tests
count_n_pswfc_radial, count_n_pswfc, G_plus_k_all)

projs = Vector{Matrix}(undef, length(basis.kpoints))
for (ik, ψk) in enumerate(ψ)
fourier_form_ik = fourier_form[ik]
structure_factor_ik = exp.(-im .* [dot(position, Gik) for Gik in G_plus_k_all[ik]])
@views for iproj = 1:size(fourier_form_ik, 2)

Check warning on line 127 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L123-L127

Added lines #L123 - L127 were not covered by tests
# TODO Pseudo-atomic wave functions need to be orthogonalized.
fourier_form_ik[:, iproj] .= (structure_factor_ik .* fourier_form_ik[:, iproj]

Check warning on line 129 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L129

Added line #L129 was not covered by tests
./ sqrt(basis.model.unit_cell_volume))
end
projs[ik] = abs2.(ψk' * fourier_form_ik)
end
projs

Check warning on line 134 in src/postprocess/dos.jl

View check run for this annotation

Codecov / codecov/patch

src/postprocess/dos.jl#L131-L134

Added lines #L131 - L134 were not covered by tests
end

"""
Plot the density of states over a reasonable range. Requires to load `Plots.jl` beforehand.
"""
function plot_dos end

function plot_ldos end

function plot_pdos end
14 changes: 14 additions & 0 deletions src/pseudo/PspUpf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,17 @@
r * (r * vloc[i] - -psp.Zion)
end
end

count_n_pswfc_radial(psp::PspUpf, l::Integer) = length(psp.r2_pswfcs[l + 1])
function count_n_pswfc_radial(psp::PspUpf)
sum(l -> count_n_pswfc_radial(psp, l), 0:psp.lmax; init=0)::Int

Check warning on line 233 in src/pseudo/PspUpf.jl

View check run for this annotation

Codecov / codecov/patch

src/pseudo/PspUpf.jl#L231-L233

Added lines #L231 - L233 were not covered by tests
end

count_n_pswfc(psp::PspUpf, l::Integer) = count_n_pswfc_radial(psp, l) * (2l + 1)
function count_n_pswfc(psp::PspUpf)
sum(l -> count_n_pswfc(psp, l), 0:psp.lmax; init=0)::Int

Check warning on line 238 in src/pseudo/PspUpf.jl

View check run for this annotation

Codecov / codecov/patch

src/pseudo/PspUpf.jl#L236-L238

Added lines #L236 - L238 were not covered by tests
end
function count_n_pswfc(psps, psp_positions)
sum(count_n_pswfc(psp) * length(positions)

Check warning on line 241 in src/pseudo/PspUpf.jl

View check run for this annotation

Codecov / codecov/patch

src/pseudo/PspUpf.jl#L240-L241

Added lines #L240 - L241 were not covered by tests
for (psp, positions) in zip(psps, psp_positions))
end
70 changes: 45 additions & 25 deletions src/terms/nonlocal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -196,44 +196,64 @@ end
Build form factors (Fourier transforms of projectors) for an atom centered at 0.
"""
function build_form_factors(psp, G_plus_k::AbstractVector{Vec3{TT}}) where {TT}
atomic_centered_function_form_factors(psp, eval_psp_projector_fourier,
count_n_proj_radial, count_n_proj, [G_plus_k])[1]
end

"""
Build Fourier transform factors of a atomic function centered at 0.
"""
function atomic_centered_function_form_factors(psp, psp_fun::Function,
count_n_fun_radial::Function,
count_n_fun::Function,
G_plus_ks::AbstractVector{<:AbstractVector{Vec3{TT}}}) where {TT}
T = real(TT)

# Pre-compute the radial parts of the non-local projectors at unique |p| to speed up
# Pre-compute the radial parts of the non-local atomic functions at unique |p| to speed up
# the form factor calculation (by a lot). Using a hash map gives O(1) lookup.

# Maximum number of projectors over angular momenta so that form factors
# for a given `p` can be stored in an `nproj x (lmax + 1)` matrix.
n_proj_max = maximum(l -> count_n_proj_radial(psp, l), 0:psp.lmax; init=0)
# Maximum number of atomic functions over angular momenta so that form factors
# for a given `p` can be stored in an `nfun x (lmax + 1)` matrix.
n_fun_per_l = map(l -> count_n_fun_radial(psp, l), 0:psp.lmax)
n_fun_max = maximum(n_fun_per_l)

radials = IdDict{T,Matrix{T}}() # IdDict for Dual compatibility
for p in G_plus_k
p_norm = norm(p)
if !haskey(radials, p_norm)
radials_p = Matrix{T}(undef, n_proj_max, psp.lmax + 1)
for l = 0:psp.lmax, iproj_l = 1:count_n_proj_radial(psp, l)
# TODO This might be faster if we do this in batches of l
# (i.e. make the inner loop run over k-points and G_plus_k)
# and did recursion over l to compute the spherical bessels
radials_p[iproj_l, l+1] = eval_psp_projector_fourier(psp, iproj_l, l, p_norm)
for G_plus_k in G_plus_ks
for p in G_plus_k
p_norm = norm(p)
if !haskey(radials, p_norm)
radials_p = Matrix{T}(undef, n_fun_max, psp.lmax + 1)
for l = 0:psp.lmax, ifun_l = 1:count_n_fun_radial(psp, l)
# TODO This might be faster if we do this in batches of l
# (i.e. make the inner loop run over k-points and G_plus_k)
# and did recursion over l to compute the spherical bessels
radials_p[ifun_l, l+1] = psp_fun(psp, ifun_l, l, p_norm)
end
radials[p_norm] = radials_p
end
radials[p_norm] = radials_p
end
end

form_factors = Matrix{Complex{T}}(undef, length(G_plus_k), count_n_proj(psp))
for (ip, p) in enumerate(G_plus_k)
radials_p = radials[norm(p)]
count = 1
for l = 0:psp.lmax, m = -l:l
# see "Fourier transforms of centered functions" in the docs for the formula
angular = (-im)^l * ylm_real(l, m, p)
for iproj_l = 1:count_n_proj_radial(psp, l)
form_factors[ip, count] = radials_p[iproj_l, l+1] * angular
count += 1
form_factors = Vector{Matrix{Complex{T}}}(undef, length(G_plus_ks))
n_fun = count_n_fun(psp)
for (ik, G_plus_k) in enumerate(G_plus_ks)
form_factors_ik = Matrix{Complex{T}}(undef, length(G_plus_k), n_fun)
for (ip, p) in enumerate(G_plus_k)
radials_p = radials[norm(p)]
count = 1
for l = 0:psp.lmax, m = -l:l
# see "Fourier transforms of centered functions" in the docs for the formula
angular = (-im)^l * ylm_real(l, m, p)
for ifun_l = 1:n_fun_per_l[l+1]
form_factors_ik[ip, count] = radials_p[ifun_l, l+1] * angular
count += 1
end
end
@assert count == n_fun + 1
end
@assert count == count_n_proj(psp) + 1
form_factors[ik] = form_factors_ik
end

form_factors
end

Expand Down
Loading