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 (ρout−ρin), i.e.
the difference in density between two subsequent SCF steps and the mix
function returns δρ, which is added to ρin to yield ρ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.844704367369 -0.58 0.0 1.50s 2 -7.856031172216 -1.95 -1.01 0.0 1.33s 3 -7.857206749077 -2.93 -1.43 0.0 55.8ms 4 -7.857296337864 -4.05 -1.75 0.0 61.3ms 5 -7.857319136840 -4.64 -2.06 0.0 102ms 6 -7.857325084669 -5.23 -2.36 0.0 55.8ms 7 -7.857326696758 -5.79 -2.66 0.0 60.3ms 8 -7.857327153270 -6.34 -2.95 0.0 157ms 9 -7.857327288370 -6.87 -3.23 0.0 65.3ms 10 -7.857327330007 -7.38 -3.49 0.0 79.0ms 11 -7.857327343295 -7.88 -3.75 0.0 584ms 12 -7.857327347658 -8.36 -4.00 0.0 56.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.
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.844704367369 -0.58 0.0 141ms 2 -7.856031172216 -1.95 -1.01 0.0 138ms 3 -7.857206749077 -2.93 -1.43 0.0 57.7ms 4 -7.857296337864 -4.05 -1.75 0.0 56.7ms 5 -7.857319136840 -4.64 -2.06 0.0 56.7ms 6 -7.857325084669 -5.23 -2.36 0.0 56.9ms 7 -7.857326696758 -5.79 -2.66 0.0 145ms 8 -7.857327153270 -6.34 -2.95 0.0 55.7ms 9 -7.857327288370 -6.87 -3.23 0.0 60.1ms 10 -7.857327330007 -7.38 -3.49 0.0 69.6ms 11 -7.857327343295 -7.88 -3.75 0.0 68.7ms 12 -7.857327347658 -8.36 -4.00 0.0 92.1ms 13 -7.857327349123 -8.83 -4.25 0.0 123ms 14 -7.857327349624 -9.30 -4.48 0.0 56.1ms 15 -7.857327349797 -9.76 -4.72 0.0 66.1ms 16 -7.857327349857 -10.22 -4.95 0.0 69.4ms