Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
include("scf_callbacks.jl")
# Struct to store some options for forward-diff / reverse-diff response
# (unused in primal calculations)
@kwdef struct ResponseOptions
verbose = false
end
"""
Obtain new density ρ by diagonalizing `ham`. Follows the policy imposed by the `bands`
data structure to determine and adjust the number of bands to be computed.
"""
function next_density(ham::Hamiltonian,
nbandsalg::NbandsAlgorithm=AdaptiveBands(ham.basis.model),
fermialg::AbstractFermiAlgorithm=default_fermialg(ham.basis.model);
eigensolver=lobpcg_hyper, ψ=nothing, eigenvalues=nothing,
occupation=nothing, kwargs...)
n_bands_converge, n_bands_compute = determine_n_bands(nbandsalg, occupation,
eigenvalues, ψ)
if isnothing(ψ)
increased_n_bands = true
else
@assert length(ψ) == length(ham.basis.kpoints)
n_bands_compute = max(n_bands_compute, maximum(ψk -> size(ψk, 2), ψ))
increased_n_bands = n_bands_compute > size(ψ[1], 2)
end
# TODO Synchronize since right now it is assumed that the same number of bands are
# computed for each k-Point
n_bands_compute = mpi_max(n_bands_compute, ham.basis.comm_kpts)
eigres = diagonalize_all_kblocks(eigensolver, ham, n_bands_compute;
ψguess=ψ, n_conv_check=n_bands_converge, kwargs...)
eigres.converged || (@warn "Eigensolver not converged" iterations=eigres.iterations)
# Check maximal occupation of the unconverged bands is sensible.
occupation, εF = compute_occupation(ham.basis, eigres.λ, fermialg;
tol_n_elec=nbandsalg.occupation_threshold)
minocc = maximum(minimum, occupation)
# TODO This is a bit hackish, but needed right now as we increase the number of bands
# to be computed only between SCF steps. Should be revisited once we have a better
# way to deal with such things in LOBPCG.
if !increased_n_bands && minocc > nbandsalg.occupation_threshold
@warn("Detected large minimal occupation $minocc. SCF could be unstable. " *
"Try switching to adaptive band selection (`nbandsalg=AdaptiveBands(model)`) " *
"or request more converged bands than $n_bands_converge (e.g. " *
"`nbandsalg=AdaptiveBands(model; n_bands_converge=$(n_bands_converge + 3)`)")
end
ρout = compute_density(ham.basis, eigres.X, occupation; nbandsalg.occupation_threshold)
(; ψ=eigres.X, eigenvalues=eigres.λ, occupation, εF, ρout, diagonalization=eigres,
n_bands_converge, nbandsalg.occupation_threshold)
end
@doc raw"""
self_consistent_field(basis; [tol, mixing, damping, ρ, ψ])
Solve the Kohn-Sham equations with a density-based SCF algorithm using damped, preconditioned
iterations where ``ρ_\text{next} = α P^{-1} (ρ_\text{out} - ρ_\text{in})``.
Overview of parameters:
- `ρ`: Initial density
- `ψ`: Initial orbitals
- `tol`: Tolerance for the density change (``\|ρ_\text{out} - ρ_\text{in}\|``)
to flag convergence. Default is `1e-6`.
- `is_converged`: Convergence control callback. Typical objects passed here are
`DFTK.ScfConvergenceDensity(tol)` (the default), `DFTK.ScfConvergenceEnergy(tol)`
or `DFTK.ScfConvergenceForce(tol)`.
- `maxiter`: Maximal number of SCF iterations
- `mixing`: Mixing method, which determines the preconditioner ``P^{-1}`` in the above equation.
Typical mixings are [`LdosMixing`](@ref), [`KerkerMixing`](@ref), [`SimpleMixing`](@ref)
or [`DielectricMixing`](@ref). Default is `LdosMixing()`
- `damping`: Damping parameter ``α`` in the above equation. Default is `0.8`.
- `nbandsalg`: By default DFTK uses `nbandsalg=AdaptiveBands(model)`, which adaptively determines
the number of bands to compute. If you want to influence this algorithm or use a predefined
number of bands in each SCF step, pass a [`FixedBands`](@ref) or [`AdaptiveBands`](@ref).
- `callback`: Function called at each SCF iteration. Usually takes care of printing the
intermediate state.
"""
@timing function self_consistent_field(
basis::PlaneWaveBasis{T};
ρ=guess_density(basis),
ψ=nothing,
tol=1e-6,
is_converged=ScfConvergenceDensity(tol),
maxiter=100,
mixing=LdosMixing(),
damping=0.8,
solver=scf_anderson_solver(),
eigensolver=lobpcg_hyper,
determine_diagtol=ScfDiagtol(),
nbandsalg::NbandsAlgorithm=AdaptiveBands(basis.model),
fermialg::AbstractFermiAlgorithm=default_fermialg(basis.model),
callback=ScfDefaultCallback(; show_damping=false),
compute_consistent_energies=true,
response=ResponseOptions(), # Dummy here, only for AD
) where {T}
# All these variables will get updated by fixpoint_map
if !isnothing(ψ)
@assert length(ψ) == length(basis.kpoints)
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
# 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, εF)
# Diagonalize `ham` to get the new state
nextstate = next_density(ham, nbandsalg, fermialg; eigensolver, ψ, eigenvalues,
occupation, miniter=1, tol=determine_diagtol(info))
ψ, eigenvalues, occupation, εF, ρout = nextstate
# Update info with results gathered so far
info = (; ham, basis, converged, stage=:iterate, algorithm="SCF",
ρin, ρout, α=damping, n_iter, nbandsalg.occupation_threshold,
nextstate..., diagonalization=[nextstate.diagonalization])
# Compute the energy of the new state
if compute_consistent_energies
energies = energy_hamiltonian(basis, ψ, occupation;
ρ=ρout, eigenvalues, εF).energies
end
info = merge(info, (; energies))
# Apply mixing and pass it the full info as kwargs
δρ = mix_density(mixing, basis, ρout - ρin; info...)
ρnext = ρin .+ T(damping) .* δρ
info = merge(info, (; ρ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.
solver(fixpoint_map, ρout, maxiter; tol=eps(T))
# We do not use the return value of solver 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, εF)
# Measure for the accuracy of the SCF
# TODO probably should be tracked all the way ...
norm_Δρ = norm(info.ρout - info.ρin) * sqrt(basis.dvol)
# Callback is run one last time with final state to allow callback to clean up
info = (; ham, basis, energies, converged, nbandsalg.occupation_threshold,
ρ=ρout, α=damping, eigenvalues, occupation, εF, info.n_bands_converge,
n_iter, ψ, info.diagonalization, stage=:finalize,
algorithm="SCF", norm_Δρ)
callback(info)
info
end