22@testsetup module Phonon
33using Test
44using DFTK
5- using DFTK: TermAtomicLocal, TermAtomicNonlocal
6- using DFTK: compute_dynmat_cart, setindex, dynmat_red_to_cart, normalize_kpoint_coordinate
5+ using DFTK: normalize_kpoint_coordinate
76using LinearAlgebra
87using ForwardDiff
9-
10- # We do not take the square root to compare eigenvalues with machine precision.
11- function squared_frequencies (matrix)
12- n, m = size (matrix, 1 ), size (matrix, 2 )
13- Ω = eigvals (reshape (matrix, n* m, n* m))
14- real (Ω)
15- end
16-
17- # Reference against automatic differentiation.
18- function reference_squared_frequencies (basis; kwargs... )
19- model = basis. model
20- n_atoms = length (model. positions)
21- n_dim = model. n_dim
22- T = eltype (model. lattice)
23- dynmat_ad = zeros (T, 3 , n_atoms, 3 , n_atoms)
24- for s = 1 : n_atoms, α = 1 : n_dim
25- displacement = zero .(model. positions)
26- displacement[s] = setindex (displacement[s], one (T), α)
27- dynmat_ad[:, :, α, s] = - ForwardDiff. derivative (zero (T)) do ε
28- lattice = convert (Matrix{eltype (ε)}, model. lattice)
29- positions = ε* displacement .+ model. positions
30- model_disp = Model (convert (Model{eltype (ε)}, model); lattice, positions)
31- # TODO : Would be cleaner with PR #675.
32- basis_disp_bs = PlaneWaveBasis (model_disp; Ecut= 5 )
33- forces = compute_forces (basis_disp_bs, nothing , nothing )
34- stack (forces)
35- end
36- end
37- hessian_ad = DFTK. dynmat_red_to_cart (model, dynmat_ad)
38- sort (squared_frequencies (hessian_ad))
39- end
8+ using FiniteDifferences
409
4110function generate_random_supercell (; max_length= 6 )
4211 n_max = min (max_length, 5 )
@@ -57,47 +26,97 @@ function generate_supercell_qpoints(; supercell_size=generate_random_supercell()
5726 (; supercell_size, qpoints)
5827end
5928
60- # Test against a reference array.
61- function test_frequencies (testcase, terms, ω_ref; tol= 1e-9 , supercell_size= [2 , 1 , 3 ])
62- model = Model (testcase. lattice, testcase. atoms, testcase. positions; terms)
63- basis_bs = PlaneWaveBasis (model; Ecut= 5 )
29+ function test_approx_frequencies (ω_uc, ω_ref; tol= 1e-10 )
30+ # Because three eigenvalues should be close to zero and the square root near
31+ # zero decrease machine accuracy, we expect at least ``3×2×2 - 3 = 9``
32+ # eigenvalues to have norm related to the accuracy of the SCF convergence
33+ # parameter and the rest to be larger.
34+ n_dim = 3
35+ n_atoms = length (ω_uc) ÷ 3
36+
37+ @test count (abs .(ω_uc - ω_ref) .< sqrt (tol)) ≥ n_dim* n_atoms - n_dim
38+ @test count (sqrt (tol) .< abs .(ω_uc - ω_ref) .< tol) ≤ n_dim
39+ end
40+
41+ function test_frequencies (model_tested, testcase; ω_ref= nothing , Ecut= 7 , kgrid= [2 , 1 , 3 ],
42+ tol= 1e-12 , randomize= false , compute_ref= nothing )
43+ supercell_size = randomize ? generate_random_supercell () : kgrid
44+ qpoints = generate_supercell_qpoints (; supercell_size). qpoints
45+ scf_tol = tol
46+ χ0_tol = scf_tol/ 10
47+ scf_kwargs = (; is_converged= ScfConvergenceDensity (scf_tol),
48+ diagtolalg= AdaptiveDiagtol (; diagtol_max= scf_tol))
6449
65- phonon = (; supercell_size, generate_supercell_qpoints (; supercell_size). qpoints)
50+ model = model_tested (testcase. lattice, testcase. atoms, testcase. positions;
51+ symmetries= false , testcase. temperature)
52+ nbandsalg = AdaptiveBands (model; occupation_threshold= 1e-10 )
53+ scf_kwargs = merge (scf_kwargs, (; nbandsalg))
54+ basis = PlaneWaveBasis (model; Ecut, kgrid)
55+ scfres = self_consistent_field (basis; scf_kwargs... )
6656
67- ω_uc = sort! (reduce (vcat, map (phonon . qpoints) do q
68- hessian = compute_dynmat_cart (basis_bs, [], [] ; q)
69- squared_frequencies (hessian)
57+ ω_uc = sort! (reduce (vcat, map (qpoints) do q
58+ dynamical_matrix = compute_dynmat (scfres ; q, tol = χ0_tol )
59+ phonon_modes_cart (basis, dynamical_matrix) . frequencies
7060 end ))
7161
72- @test norm (ω_uc - ω_ref) < tol
73- end
62+ ! isnothing (ω_ref) && return test_approx_frequencies (ω_uc, ω_ref; tol= 10 scf_tol)
7463
75- # Random test. Slow but more robust than against some reference.
76- # TODO : Will need rework for local term in future PR.
77- function test_rand_frequencies (testcase, terms; tol= 1e-9 )
78- model = Model (testcase. lattice, testcase. atoms, testcase. positions; terms)
79- basis_bs = PlaneWaveBasis (model; Ecut= 5 )
64+ supercell = create_supercell (testcase. lattice, testcase. atoms, testcase. positions,
65+ supercell_size)
66+ model_supercell = model_tested (supercell. lattice, supercell. atoms, supercell. positions;
67+ symmetries= false , testcase. temperature)
68+ nbandsalg = AdaptiveBands (model_supercell; occupation_threshold= 1e-10 )
69+ scf_kwargs = merge (scf_kwargs, (; nbandsalg))
70+ basis_supercell = PlaneWaveBasis (model_supercell; Ecut, kgrid= [1 , 1 , 1 ])
71+ scfres_supercell = self_consistent_field (basis_supercell; scf_kwargs... )
8072
81- supercell_size = supercell_size= generate_random_supercell ()
82- phonon = (; supercell_size, generate_supercell_qpoints (; supercell_size). qpoints)
73+ dynamical_matrix_sc = compute_dynmat (scfres_supercell; tol= χ0_tol)
74+ ω_sc = sort (phonon_modes_cart (basis_supercell, dynamical_matrix_sc). frequencies)
75+ test_approx_frequencies (ω_uc, ω_sc; tol= 10 scf_tol)
8376
84- ω_uc = []
85- for q in phonon. qpoints
86- hessian = compute_dynmat_cart (basis_bs, [], []; q)
87- push! (ω_uc, squared_frequencies (hessian))
88- end
89- ω_uc = sort! (collect (Iterators. flatten (ω_uc)))
77+ isnothing (compute_ref) && return
9078
91- supercell = create_supercell (testcase. lattice, testcase. atoms, testcase. positions,
92- phonon. supercell_size)
93- model_supercell = Model (supercell. lattice, supercell. atoms, supercell. positions; terms)
94- basis_supercell_bs = PlaneWaveBasis (model_supercell; Ecut= 5 )
95- hessian_supercell = compute_dynmat_cart (basis_supercell_bs, [], [])
96- ω_supercell = sort (squared_frequencies (hessian_supercell))
97- @test norm (ω_uc - ω_supercell) < tol
79+ dynamical_matrix_ref = compute_dynmat_ref (scfres_supercell. basis, model_tested; Ecut,
80+ kgrid= [1 , 1 , 1 ], scf_tol, method= compute_ref)
81+ ω_ref = sort (phonon_modes_cart (basis_supercell, dynamical_matrix_ref). frequencies)
82+
83+ test_approx_frequencies (ω_uc, ω_ref; tol= 10 scf_tol)
84+ end
9885
99- ω_ad = reference_squared_frequencies (basis_supercell_bs)
86+ # Reference results using finite differences or automatic differentiation.
87+ # This should be run by hand to obtain the reference values of the quick computations of the
88+ # tests, as they are too slow for CI runs.
89+ function compute_dynmat_ref (basis, model_tested; Ecut= 5 , kgrid= [1 ,1 ,1 ], scf_tol, method= :ad )
90+ # TODO : Cannot use symmetries: https://github.com/JuliaMolSim/DFTK.jl/issues/817
91+ @assert isone (only (basis. model. symmetries))
92+ @assert method ∈ [:ad , :fd ]
10093
101- @test norm (ω_ad - ω_supercell) < tol
94+ model = basis. model
95+ n_atoms = length (model. positions)
96+ n_dim = model. n_dim
97+ T = eltype (model. lattice)
98+ dynmat = zeros (T, 3 , n_atoms, 3 , n_atoms)
99+ scf_kwargs = (; is_converged= ScfConvergenceDensity (scf_tol),
100+ diagtolalg= AdaptiveDiagtol (; diagtol_max= scf_tol))
101+
102+ diff_fn = method == :ad ? ForwardDiff. derivative : FiniteDifferences. central_fdm (5 , 1 )
103+ for s = 1 : n_atoms, α = 1 : n_dim
104+ displacement = zero .(model. positions)
105+ displacement[s] = DFTK. setindex (displacement[s], one (T), α)
106+ dynmat[:, :, α, s] = - diff_fn (zero (T)) do ε
107+ lattice = convert (Matrix{eltype (ε)}, model. lattice)
108+ positions = ε* displacement .+ model. positions
109+ model_disp = model_tested (lattice, model. atoms, positions; symmetries= false ,
110+ model. temperature)
111+ # TODO : Would be cleaner with PR #675.
112+ basis_disp = PlaneWaveBasis (model_disp; Ecut, kgrid)
113+ nbandsalg = AdaptiveBands (model_disp; occupation_threshold= 1e-10 )
114+ scf_kwargs = merge (scf_kwargs, (; nbandsalg))
115+ scfres_disp = self_consistent_field (basis_disp; scf_kwargs... )
116+ forces = compute_forces (scfres_disp)
117+ stack (forces)
118+ end
119+ end
120+ dynmat
102121end
103122end
0 commit comments