Skip to content

Float instability of Fermi-level derivative of Chi0 for temperature & large band gap #1089

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
niklasschmitz opened this issue May 1, 2025 · 3 comments · May be fixed by #1090
Open

Float instability of Fermi-level derivative of Chi0 for temperature & large band gap #1089

niklasschmitz opened this issue May 1, 2025 · 3 comments · May be fixed by #1090

Comments

@niklasschmitz
Copy link
Collaborator

This came up now with me computing DFPT for various structures (metals, semiconductors, insulators) with temperature=1e-3 irrespective of the structure. It worked fine on Al and Si, but NaCl (salt) failed once it got to the response solver. Here's a cooked down self-contained reproducer:

using DFTK
using PseudoPotentialData
using ForwardDiff
using LinearAlgebra
DFTK.setup_threading()

pseudopotentials = PseudoFamily("dojo.nc.sr.pbe.v0_5.standard.upf")
atoms = [
    ElementPsp(:Na, pseudopotentials),
    ElementPsp(:Cl, pseudopotentials),
]
positions = [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]]

a = 10.92
lattice = (a/2) * [0 1 1; 1 0 1; 1 1 0]
Ecut = 44

function compute_stress(strain)
    new_lattice = (1 + strain) * lattice
    model = model_DFT(new_lattice, atoms, positions;
                      functionals=PBE(), kinetic_blowup=BlowupCHV(),
                      temperature=1e-3, smearing=Smearing.Gaussian())
    basis = PlaneWaveBasis(model; Ecut, kgrid=(4, 4, 4))
    scfres = self_consistent_field(basis; tol=1e-9)
    stress = compute_stresses_cart(scfres)
    tr(stress)
end

@show compute_stress(0.)  # This works fine
@show ForwardDiff.derivative(compute_stress, 0.)  # This gives NaN in response solver

Tracking this NaN down, I think I found the issue in chi0.jl:

δεF = !isnothing(model.εF) ? zero(δεF) : δocc_tot / D # no δεF when Fermi level is fixed

For an insulator with a large band gap (such as NaCl with ~5.0 eV at PBE), we get numerical underflow in all the occupation derivatives, which in this case indeed leads to exact zeros δocc_tot = 0.0 and D = 0.0 thus δocc_tot / D = NaN .

It seems the quickest fix is just to check iszero(D) before that division. What I am not sure about is whether returning zero as the Fermi level derivative in this case is even valid.

With fixed temperature, but increasing band gap the Fermi level should be converging to the center of the gap, right? That would mean that it should asymptotically observe half the derivative of the gap. Does this make sense?

@mfherbst
Copy link
Member

mfherbst commented May 2, 2025

With fixed temperature, but increasing band gap the Fermi level should be converging to the center of the gap, right?

Well this is a convention, not so much a real requirement for insulating systems. Also if δocc_tot and D are zero than nothing really changes under the perturbation.
I think what actually happens numerically is that all fpnk are actually super small, meaning that all enred are so large that the occupation derivative is effectively zero. If that's the case then all δocc and δεF should be zero.

I think we should introduce a threshold (probably related to the occupation threshold) which determines at which point we simply count the D (or maybe even the fpnk in the inner loops) as zero and effectively short-circuit to returning all δocc and δεF as zero.

@antoine-levitt
Copy link
Member

So we want to compute sum of fp(lambdak) / sum f(lambdak), which we should do by computing logs with a logsumexp-type trick and then exponentiating. Returning zero is not valid, but probably we don't care that much, at least for first order. I don't think the Fermi level of the primal is even correct in this case anyway because of the way we compute the eps_f (a bisection on a function that is probably numerically zero over a range of eps_f anyway)

So as a quick fix, either we set everybody to zero, or we detect the "effective insulator case" (gap >> temperature) and switch to insulator routines.

@mfherbst
Copy link
Member

mfherbst commented May 6, 2025

We had an offline discussion and I agree, just returning the primal is not the right thing to do.

So as a quick fix, either we set everybody to zero, or we detect the "effective insulator case" (gap >> temperature) and switch to insulator routines.

That would be my thinking, too.

@mfherbst mfherbst linked a pull request May 7, 2025 that will close this issue
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants