Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
74f8512
Add (failing) test cases for apply_χ0_4P including Fermi-level and oc…
niklasschmitz Feb 7, 2025
78b74c8
Use DifferentationInterface instead of DiffResults (#1057)
Technici4n Feb 7, 2025
82d2134
Merge branch 'master' into fermilevel-derivative
niklasschmitz Feb 11, 2025
ebeedbb
Enable AtomicLocal term computation on the GPU (#1056)
abussy Feb 12, 2025
b99ee24
Enable GPU pre-compilation (#1060)
mfherbst Feb 14, 2025
b27eb3e
Bump julia-actions/setup-julia from 1 to 2 (#1063)
dependabot[bot] Feb 17, 2025
4eb71e1
Port XC term instantiation and forces to GPU (#1061)
abussy Feb 19, 2025
cc8f69b
Minor optimization of FFTGrid initialization (#1062)
abussy Feb 19, 2025
d795f30
Tweak bookkeeping in LOBPCG (#980)
antoine-levitt Feb 24, 2025
2b8c123
Add kinetic energy density to scf state (#1069)
mfherbst Feb 26, 2025
249b422
Fix GPU runs (#1071)
abussy Mar 1, 2025
20577db
Initial AMDGPU support (#858)
mfherbst Mar 1, 2025
e3341c6
Cleanup CI (#1072)
mfherbst Mar 3, 2025
fae3b5d
Extend GPU documentation (#1073)
mfherbst Mar 4, 2025
da1afdf
Bump version to 0.7.10
mfherbst Mar 4, 2025
ff0f2c2
Update feature docs page (#1076)
mfherbst Mar 5, 2025
7009b1a
Remove references to HGH from docs (#1075)
raphaelzstone Mar 5, 2025
d9e6a25
Fix force symmetrisation bug (#1077)
mfherbst Mar 7, 2025
73db21b
Fix ForwardDiff for lattice strain DFPT response (#1054)
niklasschmitz Mar 11, 2025
a92391d
Added kweights to gradient and preconditioner in direct_minimization …
elorannug Mar 14, 2025
2f66951
Fix nonlocal forces and add test case (#1081)
Technici4n Mar 15, 2025
21938d6
Refactor and extend tests for forces (#1080)
mfherbst Mar 15, 2025
17e2d1e
Fix x density slice on tutorial (#1085)
m-schwendt Apr 15, 2025
25da786
Update publications
mfherbst May 3, 2025
8209029
Fix occupation issues for insulators modelled with temperature (#1090)
mfherbst May 12, 2025
7c54d33
CompatHelper: bump compat for DifferentiationInterface to 0.7, (keep …
github-actions[bot] May 13, 2025
41757f9
Add basic performance benchmarks (#1093)
mfherbst May 14, 2025
53d3f67
No longer abort LOBPCG for badly failing cases (#1094)
mfherbst May 15, 2025
4e44e5b
GPU optimization of LOBPCG (#1068)
abussy May 15, 2025
1bf0ad4
Bump version to 0.7.12
mfherbst May 15, 2025
69ac660
CompatHelper: bump compat for Interpolations to 0.16, (keep existing …
github-actions[bot] May 16, 2025
6dd3b5e
Fix bug with TimerOutputs (#1103)
mfherbst May 24, 2025
30f22aa
add τ in energy_hamiltonian required for some TermXc (#1100)
aouinaayoub May 24, 2025
0f4cd51
Bump version to 0.7.13
mfherbst May 27, 2025
f2f1e75
KernelAbstractions for FFTs (#1099)
abussy Jun 5, 2025
2b3dcda
GPU optimized symmetry operations (#1097)
abussy Jun 7, 2025
2ddec81
Response benchmarks (#1106)
mfherbst Jun 7, 2025
a0c64d1
Update IPI interface example (#1107)
mfherbst Jun 13, 2025
d5d4630
Add symmetry checks for CPU performance (#1108)
abussy Jun 13, 2025
6503e00
CompatHelper: bump compat for ForwardDiff to 1, (keep existing compat…
github-actions[bot] Jun 17, 2025
e2cbc06
Inexact Krylov methods and adaptive response (#1098)
mfherbst Jun 19, 2025
8028e40
Bump version to 0.7.14
mfherbst Jun 19, 2025
6a66804
Small updates to GeometryOptimizationExt (#1112)
mfherbst Jun 23, 2025
bde3c06
Drop ForwardDiff 0.10 compat (#1114)
niklasschmitz Jun 24, 2025
bbb9ced
Update Project.toml
mfherbst Jun 24, 2025
c04b641
Remove ForwardDiff complex power workarounds (#1115)
niklasschmitz Jun 24, 2025
4c9fb3f
Fix Simpson quadrature for uniform points (#1117)
Technici4n Jun 26, 2025
0d97c47
Introduce a lazy kpoint generation (#1116)
mfherbst Jun 26, 2025
861e0c9
Integrate more closely with PseudoPotentialData (#1118)
mfherbst Jul 2, 2025
658696b
Fix inconsistency between CPU and GPU in LOBPCG (#1120)
abussy Jul 29, 2025
617e2d5
CompatHelper: bump compat for AMDGPU in [weakdeps] to 2, (keep existi…
github-actions[bot] Jul 31, 2025
04b329b
Use columnwise_dots where applicable (#1127)
abussy Jul 31, 2025
f3b3589
CompatHelper: bump compat for KrylovKit to 0.10, (keep existing compa…
github-actions[bot] Aug 4, 2025
311a605
Enable precompilation on AMD GPUs (#1131)
abussy Aug 13, 2025
9626c2a
Fix Anderson on GPU (#1129)
abussy Aug 15, 2025
4a5f802
Bump actions/checkout from 4 to 5 (#1135)
dependabot[bot] Aug 26, 2025
3f7dddf
CompatHelper: bump compat for JLD2 in [weakdeps] to 0.6, (keep existi…
github-actions[bot] Aug 26, 2025
67b8ba7
Protect DFTK citation (#1139)
Technici4n Sep 4, 2025
0ea2afe
GPU| reduce bubbles and kernel overhead (#1132)
abussy Sep 5, 2025
dec431a
Make apply_kernel error when not implemented, instead of assuming zer…
niklasschmitz Sep 5, 2025
19fe479
Fix symmetries & symmetry-breaking with ForwardDiff (#1082)
niklasschmitz Sep 5, 2025
b4d0054
Include missing SCF metadata fields in forwarddiff_rules.jl (#1133)
sokenton Sep 8, 2025
a1549cd
Symmetry correction for antiferromagnetic ordering (#1144)
ClementineBarat Sep 12, 2025
6815639
Update publications and ASE links in documentation (#1147)
mfherbst Sep 12, 2025
dcb2628
Fix flaky symmetry-breaking perturbation test (#1148)
Technici4n Sep 13, 2025
176878e
Update DielectricAdjoint internal docs comments (#1149)
niklasschmitz Sep 17, 2025
cc48e31
Rename variables for DielectricAdjoint ε to ε_adj to avoid confusion …
niklasschmitz Sep 18, 2025
7fdf909
Refactoring of tests (#1143)
abussy Sep 19, 2025
08fbc2a
Change Windows test mode from 'latest' to 'stable' (#1151)
Technici4n Sep 19, 2025
80ffc59
Update to PDOS and projector computations (#1140)
fsicignano Sep 25, 2025
38af704
Bump version to 0.7.17
mfherbst Sep 25, 2025
feb4247
Disable AMDGPU precompilation if !functional (#1159)
Technici4n Oct 1, 2025
cb991bc
Fix DFPT wrt temperature (#1156)
antoine-levitt Oct 8, 2025
1a52528
Fix DivideAndConquer in Julia 1.12
Technici4n Oct 8, 2025
12ce8da
Change diff wrt temperature test (#1167)
antoine-levitt Oct 9, 2025
9b492b7
Document TDD workflow and cleanup testing docs (#1165)
Technici4n Oct 9, 2025
aceadf3
Add AD-DFPT elastic constants example (#1162)
niklasschmitz Oct 9, 2025
dab2281
Bump verison to 0.7.18
mfherbst Oct 9, 2025
deb71ee
Make SCF runs optionally reproducible by providing a seed (#1161)
Technici4n Oct 10, 2025
9b041f4
ForwardDiff tests: Use Val(:tag) in Dual type (#1170)
niklasschmitz Oct 13, 2025
b9cae64
Optimize uniform Simpson (#1171)
abussy Oct 14, 2025
b6e0873
Fix symmetries, round 2: uniform scaling (#1169)
Technici4n Oct 15, 2025
ce12859
Woraround for brittle SVD on AMD GPUs (#1173)
abussy Oct 20, 2025
15a244b
Add TimerOutputs annotation to SCF ForwardDiff rule (#1176)
niklasschmitz Oct 21, 2025
9447b6e
Various small fixes and interface improvements (#1179)
mfherbst Oct 23, 2025
9420647
Bump actions/upload-artifact from 4 to 5 (#1181)
dependabot[bot] Oct 27, 2025
10fe5f3
Inexact GMRES: Check for convergence of initial residual and remark f…
bonans Oct 28, 2025
e878a3b
Fix slow compute_density with FD (#1183)
abussy Oct 28, 2025
e412e0e
Add a warning when the NLCC causes negative Fermi hole curvature (#1184)
Technici4n Oct 31, 2025
bd7d25b
Qualify import of `Base.Matrix` and `Base.Array` to avoid warnings fr…
niklasschmitz Nov 4, 2025
b0fac72
ForwardDiff wrappers to reduce compilation time (#1182)
abussy Nov 10, 2025
a3c1d1f
Ensure contiguous occupations (#1189)
abussy Nov 11, 2025
820e29c
Increase GPU robustness of LOBPCG (#1185)
abussy Nov 11, 2025
e377f9d
Add Hubbard corrections and DFT+U (#1158)
fsicignano Nov 14, 2025
369ada7
Fix occupation masks and add tests (#1190)
abussy Nov 15, 2025
099de1e
CompatHelper: bump compat for Spglib to 1, (keep existing compat) (#1…
github-actions[bot] Nov 15, 2025
a33f68c
Add (failing) test cases for apply_χ0_4P including Fermi-level and oc…
niklasschmitz Feb 7, 2025
7339e50
Merge branch 'master' into fermilevel-derivative
niklasschmitz Nov 18, 2025
e9ea3d6
Merge branch 'master' into fermilevel-derivative
niklasschmitz Dec 19, 2025
942acaf
Merge branch 'master' into fermilevel-derivative
niklasschmitz Jan 6, 2026
0835325
Merge branch 'master' into fermilevel-derivative
niklasschmitz Feb 6, 2026
2ad01ce
Refactor find_HOMO_LUMO to use its indices consistently
niklasschmitz Mar 18, 2026
684610d
Merge branch 'master' into fermilevel-derivative
niklasschmitz Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/occupation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,21 @@ end


"""
Estimate a Fermi level by assuming (a) integer occupation and (b) an equal number of bands
is filled for each k-point. This is largely the setting for occupation without temperature.
Note, that while this function can be used in cases with spin or with temperature, there
is no guarantee that the Fermi-level estimated by this function *is* the actual Fermi level.
Therefore this function should generally only be used as a starting point for other routines.
Find the HOMO and LUMO levels and their local (k-point, band) indices, assuming
(a) integer occupation and (b) an equal number of bands is filled for each k-point.
Returns `(; HOMO, LUMO, ik_HOMO, n_HOMO, ik_LUMO, n_LUMO)`.
`LUMO`, `ik_LUMO`, `n_LUMO` are `nothing` if no unoccupied bands are available.
The `ik_*` indices refer to this MPI rank's local k-points.
"""
function guess_fermi_level_intocc_(basis::PlaneWaveBasis, eigenvalues)
function find_HOMO_LUMO(basis::PlaneWaveBasis, eigenvalues)
filled_occ = filled_occupation(basis.model)
n_spin = basis.model.n_spin_components
n_fill = div(basis.model.n_electrons, n_spin * filled_occ, RoundUp)

# Highest occupied energy level
HOMO = maximum([εk[n_fill] for εk in eigenvalues])
HOMO = mpi_max(HOMO, basis.comm_kpts)
ik_HOMO = argmax(ik -> eigenvalues[ik][n_fill], eachindex(eigenvalues))

# Lowest unoccupied energy level: not all k-points might have at least n_fill+1
# energy levels so we have to take care of that by specifying init to minimum
Expand All @@ -197,6 +198,27 @@ function guess_fermi_level_intocc_(basis::PlaneWaveBasis, eigenvalues)
LUMO = mpi_min(LUMO, basis.comm_kpts)

if LUMO == typemax(HOMO)
(; HOMO, LUMO=nothing, ik_HOMO, n_HOMO=n_fill,
ik_LUMO=nothing, n_LUMO=nothing)
else
ik_LUMO = argmin(eachindex(eigenvalues)) do ik
minimum(eigenvalues[ik][n_fill+1:end]; init=typemax(HOMO))
end
n_LUMO = n_fill + argmin(eigenvalues[ik_LUMO][n_fill+1:end])
(; HOMO, LUMO, ik_HOMO, n_HOMO=n_fill, ik_LUMO, n_LUMO)
end
end

"""
Estimate a Fermi level by assuming (a) integer occupation and (b) an equal number of bands
is filled for each k-point. This is largely the setting for occupation without temperature.
Note, that while this function can be used in cases with spin or with temperature, there
is no guarantee that the Fermi-level estimated by this function *is* the actual Fermi level.
Therefore this function should generally only be used as a starting point for other routines.
"""
function guess_fermi_level_intocc_(basis::PlaneWaveBasis, eigenvalues)
(; HOMO, LUMO) = find_HOMO_LUMO(basis, eigenvalues)
if isnothing(LUMO)
HOMO + 1 # Just to make sure the εF is a sane number and above HOMO
else
(HOMO + LUMO) / 2
Expand Down
31 changes: 23 additions & 8 deletions src/response/chi0.jl
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ function compute_δocc!(δocc, basis::PlaneWaveBasis{T}, ψ, εF, ε, δHψ, δt
end
D = mpi_sum(D, basis.comm_kpts)

if isnothing(model.εF) # εF === nothing means that Fermi level is fixed by model
if isnothing(model.εF) # εF === nothing means that Fermi level is not fixed by model
# Compute δεF from δ ∑ occ = 0…
δocc_tot = mpi_sum(sum(basis.kweights .* sum.(δocc)), basis.comm_kpts)
δεF = -δocc_tot / D
Expand All @@ -344,6 +344,20 @@ function compute_δocc!(δocc, basis::PlaneWaveBasis{T}, ψ, εF, ε, δHψ, δt
δocc[ik][n] -= fpnk * δεF / temperature
end
end
elseif isnothing(model.εF)
# Effective insulator (zero temperature or large gap): occupations don't change,
# but εF = (HOMO + LUMO) / 2 shifts with the eigenvalues.
# We reuse find_HOMO_LUMO to identify the relevant (k, band) indices,
# consistent with guess_fermi_level_intocc_.
(; ik_HOMO, n_HOMO, ik_LUMO, n_LUMO) = find_HOMO_LUMO(basis, ε)
if !isnothing(ik_LUMO)
δε_HOMO = real(dot(ψ[ik_HOMO][:, n_HOMO], δHψ[ik_HOMO][:, n_HOMO]))
δε_LUMO = real(dot(ψ[ik_LUMO][:, n_LUMO], δHψ[ik_LUMO][:, n_LUMO]))
# In MPI, each rank computes δε at its local argmax/argmin;
# the global extremum picks the correct derivative.
δεF = (mpi_max(δε_HOMO, basis.comm_kpts) +
mpi_min(δε_LUMO, basis.comm_kpts)) / 2
end
end

(; δocc, δεF)
Expand Down Expand Up @@ -435,7 +449,7 @@ end

"""
Compute the orbital and occupation changes as a result of applying the ``χ_0`` superoperator
to the Hamiltonian change `δH` represented by the matrix-vector products `δHψ`.
to the Hamiltonian change `δH` represented by the matrix-vector products `δHψ`.
"""
@views @timing function apply_χ0_4P(ham, ψ, occupation, εF, eigenvalues, δHψ;
δtemperature=zero(eltype(ham.basis)),
Expand Down Expand Up @@ -469,16 +483,17 @@ to the Hamiltonian change `δH` represented by the matrix-vector products `δHψ
bandtol_minus_q_occ = [bandtol_occ[k_to_k_minus_q[ik]] for ik in 1:length(basis.kpoints)]
@assert bandtolalg.occupation_threshold == occupation_threshold

# First we compute δoccupation. We only need to do this for the actually occupied
# orbitals. So we make a fresh array padded with zeros, but only alter the elements
# corresponding to the occupied orbitals. (Note both compute_δocc! and compute_δψ!
# assume that the first array argument has already been initialised to zero).
# First we compute δoccupation and δεF. We pass the full eigenvalues, ψ and δHψ
# (not just the occupied subset) so that compute_δocc! can compute δεF for
# effective insulators, where εF = (HOMO + LUMO) / 2 requires LUMO data.
# For the metallic/finite-temperature path the extra empty bands contribute ≈ 0
# (exponentially small occupation derivatives). Both compute_δocc! and compute_δψ!
# assume that the first array argument has already been initialised to zero.
# For phonon calculations when q ≠ 0, we do not use δoccupation, and compute directly
# the full perturbation δψ.
δoccupation = zero.(occupation)
if iszero(q)
δocc_occ = [δoccupation[ik][maskk] for (ik, maskk) in enumerate(mask_occ)]
(; δεF) = compute_δocc!(δocc_occ, basis, ψ_occ, εF, ε_occ, δHψ_minus_q_occ, δtemperature)
(; δεF) = compute_δocc!(δoccupation, basis, ψ, εF, eigenvalues, δHψ, δtemperature)
else
# When δH is not periodic, δH ψnk is a Bloch wave at k+q and ψnk at k,
# so that δεnk = <ψnk|δH|ψnk> = 0 and there is no occupation shift
Expand Down
36 changes: 25 additions & 11 deletions test/chi0.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@testmodule Chi0 begin
using Test
using ComponentArrays
using DFTK
using DFTK: mpi_mean!
using LinearAlgebra
Expand Down Expand Up @@ -33,7 +34,9 @@ function test_chi0(testcase; symmetries=false, temperature=0, spin_polarization=
ham0 = energy_hamiltonian(basis, nothing, nothing; ρ=ρ0).ham
nbandsalg = is_εF_fixed ? FixedBands(; n_bands_converge=6) : AdaptiveBands(model)
res = DFTK.next_density(ham0, nbandsalg; tol, eigensolver)
scfres = (; ham=ham0, res...)
scfres = (; basis, ham=ham0, res...)

bandtolalg = DFTK.BandtolBalanced(scfres)

# create external small perturbation εδV
n_spin = model.n_spin_components
Expand All @@ -46,24 +49,34 @@ function test_chi0(testcase; symmetries=false, temperature=0, spin_polarization=
@test δV_sym ≈ δV
end

function compute_ρ_FD(ε)
function compute_finite_perturbation(ε)
term_builder = basis -> DFTK.TermExternal(ε * δV)
model = model_DFT(testcase.lattice, testcase.atoms, testcase.positions;
functionals, model_kwargs..., extra_terms=[term_builder])
basis = PlaneWaveBasis(model; basis_kwargs...)
ham = energy_hamiltonian(basis, nothing, nothing; ρ=ρ0).ham
res = DFTK.next_density(ham, nbandsalg; tol, eigensolver)
res.ρout
(; ρout, occupation, εF) = DFTK.next_density(ham, nbandsalg; tol, eigensolver)
ComponentArray(; ρ=ρout, occupation, εF)
end

# middle point finite difference for more precision
ρ1 = compute_ρ_FD(-ε)
ρ2 = compute_ρ_FD(ε)
diff_findiff = (ρ2 - ρ1) / (2ε)
res1 = compute_finite_perturbation(-ε)
res2 = compute_finite_perturbation(+ε)
diff_findiff = (res2 - res1) / (2ε)

# Test apply_χ0 and compare against finite differences
diff_applied_χ0 = apply_χ0(scfres, δV).δρ
@test norm(diff_findiff - diff_applied_χ0) < atol
diff_applied_χ0 = apply_χ0(scfres, δV)
@test norm(diff_findiff.ρ - diff_applied_χ0.δρ) < atol

# Test occupation and Fermi-level sensitivities
(; ψ, occupation, eigenvalues, εF, occupation_threshold) = scfres
q = zero(Vec3{eltype(ham0.basis)})
δHψ = DFTK.multiply_ψ_by_blochwave(basis, ψ, δV, q)
diff_applied_χ0_4P = DFTK.apply_χ0_4P(ham0, ψ, occupation, εF, eigenvalues, δHψ;
occupation_threshold, bandtolalg, q)
maximumabs(x) = maximum(abs, x)
@test maximum(maximumabs, diff_applied_χ0_4P.δoccupation - diff_findiff.occupation) < atol
@test abs(diff_applied_χ0_4P.δεF - diff_findiff.εF) < atol

# Test apply_χ0 without extra bands
ψ_occ, occ_occ = DFTK.select_occupied_orbitals(basis, scfres.ψ, scfres.occupation;
Expand All @@ -72,12 +85,13 @@ function test_chi0(testcase; symmetries=false, temperature=0, spin_polarization=

diff_applied_χ0_noextra = apply_χ0(scfres.ham, ψ_occ, occ_occ, scfres.εF, ε_occ, δV;
scfres.occupation_threshold).δρ
@test norm(diff_applied_χ0_noextra - diff_applied_χ0) < atol
@test norm(diff_applied_χ0_noextra - diff_applied_χ0.δρ) < atol

# just to cover it here
if temperature > 0
D = compute_dos(res.εF, basis, res.eigenvalues)
LDOS = compute_ldos(res.εF, basis, res.eigenvalues, res.ψ)
@test abs(sum(LDOS) * basis.dvol - sum(D)) < 1e-12
end

if !symmetries
Expand All @@ -86,7 +100,7 @@ function test_chi0(testcase; symmetries=false, temperature=0, spin_polarization=
if compute_full_χ0
χ0 = compute_χ0(ham0)
diff_computed_χ0 = reshape(χ0 * vec(δV), basis.fft_size..., n_spin)
@test norm(diff_findiff - diff_computed_χ0) < atol
@test norm(diff_findiff - diff_computed_χ0) < atol
end

# Test that apply_χ0 is self-adjoint
Expand Down
Loading