self_consistent_field function takes as the
keyword argument one function to be called after each iteration.
This function gets passed the complete internal state of the SCF
solver and can thus be used both to monitor and debug the iterations
as well as to quickly patch it with additional functionality.
This example discusses a few aspects of the
taking again our favourite silicon example.
We setup silicon in an LDA model using the ASE interface to build the silicon lattice, see Creating slabs with ASE for more details.
using DFTK using PyCall silicon = pyimport("ase.build").bulk("Si") atoms = load_atoms(silicon) atoms = [ElementPsp(el.symbol, psp=load_psp(el.symbol, functional="lda")) => position for (el, position) in atoms] lattice = load_lattice(silicon); model = model_LDA(lattice, atoms) kgrid = [3, 3, 3] # k-point grid Ecut = 5 # kinetic energy cutoff in Hartree basis = PlaneWaveBasis(model, Ecut; kgrid=kgrid);
DFTK already defines a few callback functions for standard
tasks. One example is the usual convergence table,
which is defined in the callback
Another example is
ScfPlotTrace, which records the total
energy at each iteration and uses it to plot the convergence
of the SCF graphically once it is converged.
For details and other callbacks
!!! note "Callbacks are not exported"
Callbacks are not exported from the DFTK namespace as of now,
so you will need to use them, e.g., as
In this example we define a custom callback, which plots the change in density at each SCF iteration after the SCF has finished. For this we first define the empty plot canvas and an empty container for all the density differences:
using Plots p = plot(yaxis=:log) density_differences = Float64;
The callback function itself gets passed a named tuple
similar to the one returned by
which contains the input and output density of the SCF step
ρout. Since the callback gets called
both during the SCF iterations as well as after convergence
self_consistent_field finishes we can both
collect the data and initiate the plotting in one function.
using LinearAlgebra function plot_callback(info) if info.stage == :finalize plot!(p, density_differences, label="|ρout - ρin|", markershape=:x) else push!(density_differences, norm(info.ρout.real - info.ρin.real)) end info end callback = DFTK.ScfDefaultCallback() ∘ plot_callback;
Notice that for constructing the
callback function we chained the
(which does the plotting) with the
ScfDefaultCallback, such that when using
plot_callback function with
self_consistent_field we still get the usual
convergence table printed. We run the SCF with this callback ...
scfres = self_consistent_field(basis, tol=1e-8, callback=callback);
n Energy Eₙ-Eₙ₋₁ ρout-ρin Diag --- --------------- --------- -------- ---- 1 -7.844911905950 NaN 1.97e-01 4.0 2 -7.850326616771 -5.41e-03 2.93e-02 1.0 3 -7.850617259203 -2.91e-04 2.81e-03 1.5 4 -7.850646790884 -2.95e-05 1.26e-03 2.5 5 -7.850647351232 -5.60e-07 5.91e-04 1.0 6 -7.850647505110 -1.54e-07 4.22e-05 1.0 7 -7.850647511556 -6.45e-09 5.46e-06 2.2
... and show the plot
info object passed to the callback contains not just the densities
but also the complete Bloch wave (in
and so on.
for all currently available keys.
!!! tip "Debugging with callbacks"
Very handy for debugging SCF algorithms is to employ callbacks
@infiltrate from Infiltrator.jl
to interactively monitor what is happening each SCF step.