/
self_consistent_field.jl
149 lines (128 loc) · 6.14 KB
/
self_consistent_field.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
include("scf_callbacks.jl")
function default_n_bands(model)
min_n_bands = div(model.n_electrons, filled_occupation(model), RoundUp)
n_extra = model.temperature == 0 ? 0 : max(4, ceil(Int, 0.2 * min_n_bands))
min_n_bands + n_extra
end
"""
Obtain new density ρ by diagonalizing `ham`.
"""
function next_density(ham::Hamiltonian;
n_bands=default_n_bands(ham.basis.model),
ψ=nothing, n_ep_extra=3,
eigensolver=lobpcg_hyper, kwargs...)
if ψ !== nothing
@assert length(ψ) == length(ham.basis.kpoints)
for ik in 1:length(ham.basis.kpoints)
@assert size(ψ[ik], 2) == n_bands + n_ep_extra
end
end
# Diagonalize
eigres = diagonalize_all_kblocks(eigensolver, ham, n_bands + n_ep_extra; guess=ψ,
n_conv_check=n_bands, kwargs...)
eigres.converged || (@warn "Eigensolver not converged" iterations=eigres.iterations)
# Update density from new ψ
occupation, εF = compute_occupation(ham.basis, eigres.λ)
ρout = compute_density(ham.basis, eigres.X, occupation)
(ψ=eigres.X, eigenvalues=eigres.λ, occupation=occupation, εF=εF,
ρout=ρout, diagonalization=eigres)
end
"""
Solve the Kohn-Sham equations with a SCF algorithm, starting at ρ.
"""
@timing function self_consistent_field(basis::PlaneWaveBasis;
n_bands=default_n_bands(basis.model),
ρ=guess_density(basis),
ψ=nothing,
tol=1e-6,
maxiter=100,
solver=scf_nlsolve_solver(),
eigensolver=lobpcg_hyper,
n_ep_extra=3,
determine_diagtol=ScfDiagtol(),
damping=0.8, # Damping parameter
mixing=LdosMixing(),
is_converged=ScfConvergenceEnergy(tol),
callback=ScfDefaultCallback(),
compute_consistent_energies=true,
enforce_symmetry=false,
)
T = eltype(basis)
model = basis.model
# All these variables will get updated by fixpoint_map
if ψ !== nothing
@assert length(ψ) == length(basis.kpoints)
for ik in 1:length(basis.kpoints)
@assert size(ψ[ik], 2) == n_bands + n_ep_extra
end
end
occupation = nothing
eigenvalues = nothing
ρout = ρ
εF = nothing
n_iter = 0
energies = nothing
ham = nothing
info = (n_iter=0, ρin=ρ) # Populate info with initial values
converged = false
# We do density mixing in the real representation
# TODO support other mixing types
function fixpoint_map(ρin)
converged && return ρin # No more iterations if convergence flagged
n_iter += 1
# Build next Hamiltonian, diagonalize it, get ρout
if n_iter == 1 # first iteration
_, ham = energy_hamiltonian(basis, nothing, nothing;
ρ=ρin, eigenvalues=nothing, εF=nothing)
else
# Note that ρin is not the density of ψ, and the eigenvalues
# are not the self-consistent ones, which makes this energy non-variational
energies, ham = energy_hamiltonian(basis, ψ, occupation;
ρ=ρin, eigenvalues=eigenvalues, εF=εF)
end
# Diagonalize `ham` to get the new state
nextstate = next_density(ham; n_bands=n_bands, ψ=ψ, eigensolver=eigensolver,
miniter=1, tol=determine_diagtol(info),
n_ep_extra=n_ep_extra)
ψ, eigenvalues, occupation, εF, ρout = nextstate
if enforce_symmetry
ρout = DFTK.symmetrize_ρ(basis, ρout)
end
# Update info with results gathered so far
info = (ham=ham, basis=basis, converged=converged, stage=:iterate, algorithm="SCF",
ρin=ρin, ρout=ρout, α=damping, n_iter=n_iter, n_ep_extra=n_ep_extra,
nextstate..., diagonalization=[nextstate.diagonalization])
# Compute the energy of the new state
if compute_consistent_energies
energies, _ = energy_hamiltonian(basis, ψ, occupation;
ρ=ρout, eigenvalues=eigenvalues, εF=εF)
end
info = merge(info, (energies=energies, ))
# Apply mixing and pass it the full info as kwargs
δρ = mix_density(mixing, basis, ρout - ρin; info...)
ρnext = ρin .+ T(damping) .* δρ
if enforce_symmetry
ρnext = DFTK.symmetrize_ρ(basis, ρnext)
end
info = merge(info, (; ρnext=ρnext))
callback(info)
is_converged(info) && (converged = true)
ρnext
end
# Tolerance and maxiter are only dummy here: Convergence is flagged by is_converged
# inside the fixpoint_map. Also we do not use the return value of fpres but rather the
# one that got updated by fixpoint_map
fpres = solver(fixpoint_map, ρout, maxiter; tol=eps(T))
# We do not use the return value of fpres but rather the one that got updated by fixpoint_map
# ψ is consistent with ρout, so we return that. We also perform
# a last energy computation to return a correct variational energy
energies, ham = energy_hamiltonian(basis, ψ, occupation;
ρ=ρout, eigenvalues=eigenvalues, εF=εF)
# Callback is run one last time with final state to allow callback to clean up
info = (ham=ham, basis=basis, energies=energies, converged=converged,
ρ=ρout, eigenvalues=eigenvalues, occupation=occupation, εF=εF,
n_iter=n_iter, n_ep_extra=n_ep_extra, ψ=ψ, diagonalization=info.diagonalization,
stage=:finalize, algorithm="SCF")
callback(info)
info
end