Custom solvers
In this example, we show how to define custom solvers. Our system will again be silicon, because we are not very imaginative
using DFTK
using LinearAlgebra
using PseudoPotentialData
using AtomsBuilder
# We take very (very) crude parameters
pseudopotentials = PseudoFamily("dojo.nc.sr.lda.v0_4_1.standard.upf")
model = model_DFT(bulk(:Si); functionals=LDA(), pseudopotentials)
basis = PlaneWaveBasis(model; Ecut=5, kgrid=[1, 1, 1]);
We define our custom fix-point solver: simply a damped fixed-point
function my_fp_solver(f, x0, info0; maxiter)
mixing_factor = .7
x = x0
info = info0
for n = 1:maxiter
fx, info = f(x, info)
if info.converged || info.timedout
break
end
x = x + mixing_factor * (fx - x)
end
(; fixpoint=x, info)
end;
Note that the fixpoint map f
operates on an auxiliary variable info
for state bookkeeping. Early termination criteria are flagged from inside the function f
using boolean flags info.converged
and info.timedout
. For control over these criteria, see the is_converged
and maxtime
keyword arguments of self_consistent_field
.
Our eigenvalue solver just forms the dense matrix and diagonalizes it explicitly (this only works for very small systems)
function my_eig_solver(A, X0; maxiter, tol, kwargs...)
n = size(X0, 2)
A = Array(A)
E = eigen(A)
λ = E.values[1:n]
X = E.vectors[:, 1:n]
(; λ, X, residual_norms=[], n_iter=0, converged=true, n_matvec=0)
end;
Finally we also define our custom mixing scheme. It will be a mixture of simple mixing (for the first 2 steps) and than default to Kerker mixing. In the mixing interface δF
is $(ρ_\text{out} - ρ_\text{in})$, i.e. the difference in density between two subsequent SCF steps and the mix
function returns $δρ$, which is added to $ρ_\text{in}$ to yield $ρ_\text{next}$, the density for the next SCF step.
struct MyMixing
n_simple # Number of iterations for simple mixing
end
MyMixing() = MyMixing(2)
function DFTK.mix_density(mixing::MyMixing, basis, δF; n_iter, kwargs...)
if n_iter <= mixing.n_simple
return δF # Simple mixing -> Do not modify update at all
else
# Use the default KerkerMixing from DFTK
DFTK.mix_density(KerkerMixing(), basis, δF; kwargs...)
end
end
That's it! Now we just run the SCF with these solvers
scfres = self_consistent_field(basis;
tol=1e-4,
solver=my_fp_solver,
eigensolver=my_eig_solver,
mixing=MyMixing());
n Energy log10(ΔE) log10(Δρ) Diag Δtime
--- --------------- --------- --------- ---- ------
1 -7.824595558215 -0.55 0.0 271ms
2 -7.852678862895 -1.55 -0.92 0.0 334ms
3 -7.857032699882 -2.36 -1.38 0.0 84.1ms
4 -7.857258210502 -3.65 -1.70 0.0 82.9ms
5 -7.857310659971 -4.28 -2.00 0.0 83.4ms
6 -7.857323136747 -4.90 -2.30 0.0 71.9ms
7 -7.857326229041 -5.51 -2.59 0.0 72.9ms
8 -7.857327035177 -6.09 -2.88 0.0 189ms
9 -7.857327256939 -6.65 -3.15 0.0 71.5ms
10 -7.857327321198 -7.19 -3.41 0.0 70.4ms
11 -7.857327340706 -7.71 -3.67 0.0 81.8ms
12 -7.857327346865 -8.21 -3.92 0.0 113ms
13 -7.857327348871 -8.70 -4.16 0.0 84.7ms
Note that the default convergence criterion is the difference in density. When this gets below tol
, the fixed-point solver terminates. You can also customize this with the is_converged
keyword argument to self_consistent_field
, as shown below.
Customizing the convergence criterion
Here is an example of a defining a custom convergence criterion and specifying it using the is_converged
callback keyword to self_consistent_field
.
function my_convergence_criterion(info)
tol = 1e-10
length(info.history_Etot) < 2 && return false
ΔE = (info.history_Etot[end-1] - info.history_Etot[end])
ΔE < tol
end
scfres2 = self_consistent_field(basis;
solver=my_fp_solver,
is_converged=my_convergence_criterion,
eigensolver=my_eig_solver,
mixing=MyMixing());
n Energy log10(ΔE) log10(Δρ) Diag Δtime
--- --------------- --------- --------- ---- ------
1 -7.824595558215 -0.55 0.0 155ms
2 -7.852678862895 -1.55 -0.92 0.0 152ms
3 -7.857032699882 -2.36 -1.38 0.0 181ms
4 -7.857258210502 -3.65 -1.70 0.0 72.8ms
5 -7.857310659971 -4.28 -2.00 0.0 82.7ms
6 -7.857323136747 -4.90 -2.30 0.0 83.6ms
7 -7.857326229041 -5.51 -2.59 0.0 87.3ms
8 -7.857327035177 -6.09 -2.88 0.0 85.0ms
9 -7.857327256939 -6.65 -3.15 0.0 136ms
10 -7.857327321198 -7.19 -3.41 0.0 71.2ms
11 -7.857327340706 -7.71 -3.67 0.0 79.4ms
12 -7.857327346865 -8.21 -3.92 0.0 82.7ms
13 -7.857327348871 -8.70 -4.16 0.0 84.8ms
14 -7.857327349541 -9.17 -4.40 0.0 115ms
15 -7.857327349769 -9.64 -4.64 0.0 100ms
16 -7.857327349848 -10.10 -4.87 0.0 80.6ms