Comparison of DFT solvers

We compare four different approaches for solving the DFT minimisation problem, namely a density-based SCF, a potential-based SCF, direct minimisation and Newton.

First we setup our problem

using DFTK
using LinearAlgebra

a = 10.26  # Silicon lattice constant in Bohr
lattice = a / 2 * [[0 1 1.];
                   [1 0 1.];
                   [1 1 0.]]
Si = ElementPsp(:Si; psp=load_psp("hgh/lda/Si-q4"))
atoms     = [Si, Si]
positions = [ones(3)/8, -ones(3)/8]

model = model_LDA(lattice, atoms, positions)
basis = PlaneWaveBasis(model; Ecut=5, kgrid=[3, 3, 3])

# Convergence we desire in the density
tol = 1e-6
1.0e-6

Density-based self-consistent field

scfres_scf = self_consistent_field(basis; tol);
n     Energy            log10(ΔE)   log10(Δρ)   Diag   Δtime
---   ---------------   ---------   ---------   ----   ------
  1   -7.847756544273                   -0.70    4.5   35.3ms
  2   -7.852403528783       -2.33       -1.53    1.0   19.0ms
  3   -7.852610395495       -3.68       -2.55    1.2   20.0ms
  4   -7.852645296118       -4.46       -2.77    2.0   24.5ms
  5   -7.852646045339       -6.13       -2.86    1.2   20.0ms
  6   -7.852646666423       -6.21       -3.94    1.0   18.6ms
  7   -7.852646686208       -7.70       -4.51    2.0   23.2ms
  8   -7.852646686705       -9.30       -5.46    1.2   19.6ms
  9   -7.852646686724      -10.72       -5.41    2.2   24.5ms
 10   -7.852646686729      -11.27       -6.01    1.0   19.0ms

Potential-based SCF

scfres_scfv = DFTK.scf_potential_mixing(basis; tol);
n     Energy            log10(ΔE)   log10(Δρ)   α      Diag   Δtime
---   ---------------   ---------   ---------   ----   ----   ------
  1   -7.847742880307                   -0.70           4.8   36.1ms
  2   -7.852560037552       -2.32       -1.62   0.80    2.0   22.5ms
  3   -7.852640557742       -4.09       -2.71   0.80    1.0   17.1ms
  4   -7.852646523033       -5.22       -3.36   0.80    2.0   22.0ms
  5   -7.852646681379       -6.80       -4.35   0.80    1.5   19.8ms
  6   -7.852646686618       -8.28       -4.83   0.80    2.2   42.5ms
  7   -7.852646686726       -9.97       -5.51   0.80    1.0   17.7ms
  8   -7.852646686729      -11.41       -6.80   0.80    1.8   20.9ms

Direct minimization

Note: Unlike the other algorithms, tolerance for this one is in the energy, thus we square the density tolerance value to be roughly equivalent.

scfres_dm = direct_minimization(basis; tol=tol^2);
n     Energy            log10(ΔE)   log10(Δρ)   Δtime
---   ---------------   ---------   ---------   ------
  1   +1.339642711599                   -1.02   45.7ms
  2   -1.695465957873        0.48       -0.63   25.4ms
  3   -3.455251673248        0.25       -0.43   33.5ms
  4   -4.660286048631        0.08       -0.55   33.2ms
  5   -6.290902913251        0.21       -0.64   33.5ms
  6   -7.155006548665       -0.06       -0.97   33.4ms
  7   -7.429532076731       -0.56       -1.52   25.2ms
  8   -7.669741929177       -0.62       -1.80   25.3ms
  9   -7.728812920525       -1.23       -1.95   25.2ms
 10   -7.777966819068       -1.31       -1.96   25.3ms
 11   -7.811904316156       -1.47       -2.07   25.4ms
 12   -7.831034187211       -1.72       -2.38   25.4ms
 13   -7.845039616951       -1.85       -2.23   25.2ms
 14   -7.847391979402       -2.63       -2.98   25.3ms
 15   -7.848966658762       -2.80       -2.73   25.3ms
 16   -7.850857721567       -2.72       -3.40   25.3ms
 17   -7.852024719079       -2.93       -3.21   25.6ms
 18   -7.852427997472       -3.39       -3.71   25.3ms
 19   -7.852573322360       -3.84       -3.91   25.2ms
 20   -7.852621243407       -4.32       -4.10   25.1ms
 21   -7.852637839899       -4.78       -4.35   25.4ms
 22   -7.852643701397       -5.23       -4.64   25.3ms
 23   -7.852645528771       -5.74       -4.81   25.2ms
 24   -7.852646258490       -6.14       -5.08   25.4ms
 25   -7.852646525632       -6.57       -5.32   25.5ms
 26   -7.852646632049       -6.97       -5.25   25.6ms
 27   -7.852646671435       -7.40       -5.37   26.0ms
 28   -7.852646682255       -7.97       -6.05   25.5ms
 29   -7.852646685380       -8.51       -5.92   25.5ms
 30   -7.852646686260       -9.06       -6.01   25.4ms
 31   -7.852646686566       -9.51       -6.31   25.5ms
 32   -7.852646686682       -9.94       -6.72   25.4ms
 33   -7.852646686715      -10.48       -6.90   25.5ms
 34   -7.852646686725      -11.01       -7.06   25.4ms
 35   -7.852646686728      -11.52       -7.69   25.3ms
 36   -7.852646686729      -11.86       -7.85   25.4ms
 37   -7.852646686729      -12.24       -8.03   25.3ms
 38   -7.852646686730      -12.63       -8.33   25.3ms
 39   -7.852646686730      -13.01       -8.76   25.3ms
 40   -7.852646686730      -13.36       -8.82   25.4ms
 41   -7.852646686730      -13.88       -9.20   25.6ms
 42   -7.852646686730      -14.45       -9.16   25.4ms
 43   -7.852646686730   +    -Inf       -9.59   25.3ms
 44   -7.852646686730      -14.45       -9.53   25.3ms
 45   -7.852646686730   +    -Inf       -9.76   25.4ms
 46   -7.852646686730   +    -Inf      -10.15   33.6ms
┌ Warning: DM not converged.
@ DFTK ~/work/DFTK.jl/DFTK.jl/src/scf/scf_callbacks.jl:60

Newton algorithm

Start not too far from the solution to ensure convergence: We run first a very crude SCF to get close and then switch to Newton.

scfres_start = self_consistent_field(basis; tol=0.5);
n     Energy            log10(ΔE)   log10(Δρ)   Diag   Δtime
---   ---------------   ---------   ---------   ----   ------
  1   -7.847789314938                   -0.70    4.8   36.2ms

Remove the virtual orbitals (which Newton cannot treat yet)

ψ = DFTK.select_occupied_orbitals(basis, scfres_start.ψ, scfres_start.occupation).ψ
scfres_newton = newton(basis, ψ; tol);
n     Energy            log10(ΔE)   log10(Δρ)   Δtime
---   ---------------   ---------   ---------   ------
  1   -7.852645851205                   -1.63    403ms
  2   -7.852646686730       -6.08       -3.69    285ms
  3   -7.852646686730      -13.21       -7.20    143ms

Comparison of results

println("|ρ_newton - ρ_scf|  = ", norm(scfres_newton.ρ - scfres_scf.ρ))
println("|ρ_newton - ρ_scfv| = ", norm(scfres_newton.ρ - scfres_scfv.ρ))
println("|ρ_newton - ρ_dm|   = ", norm(scfres_newton.ρ - scfres_dm.ρ))
|ρ_newton - ρ_scf|  = 9.84685640649203e-7
|ρ_newton - ρ_scfv| = 6.792426351774256e-7
|ρ_newton - ρ_dm|   = 9.527468030590699e-10