Skip to content

Conversation

@abussy
Copy link
Collaborator

@abussy abussy commented Nov 11, 2025

This PR adds checks to ensure that occupations are contiguous. In various places in the code, it is simply assumed that occupied and empty orbitals are clearly separated (e.g. here or here). This is currently not a safe assumption, as it is not enforced.

This PR adds a check on eigenvalues monotonocity when computing the occupations. Additionally, a helper function providing masks in the form of ranges (useful for taking views into GPU arrays) was added. Finally, the creation of supercells now enforces consistent reordering of the eigenvalues and wave-functions.

Note: range masks are not used in densities.jl, as the compute_density() function is sometime hijacked in other contexts, where the assumption on occupations does not hold (e.g. here)

This PR is a necessary fix for #1187 tests to pass.

Copy link
Member

@mfherbst mfherbst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @abussy

@mfherbst mfherbst enabled auto-merge (squash) November 11, 2025 17:14
@mfherbst mfherbst merged commit 99ac78a into JuliaMolSim:master Nov 11, 2025
10 checks passed
@antoine-levitt
Copy link
Member

This is wrong when using methfessel paxton smearing

@abussy
Copy link
Collaborator Author

abussy commented Nov 12, 2025

I can see that this could fail with Methfessel Paxton smearing:

DFTK.jl/src/occupation.jl

Lines 221 to 223 in 3551ad3

n = count(occ_i -> abs(occ_i) > occupation_threshold, occ)
# Check that all occupied elements are contiguous, otherwise range is wrong
@assert all(occ[1:n] .> occupation_threshold)

Looking at the rest of the code, it is still assumed that there is a clear distinction between occupied and empty orbitals (see links in initial post). There, the last occupation with abs(occ) > threshold is considered to be the boundary.

I think that it is still healthy to assert that eigenvalues are monotonic down to epsilon, and to enforce it when building supercells. However, the occupied_empty_masks() function can be re-written to reflect the same convention on occupation boundary.

@mfherbst
Copy link
Member

mfherbst commented Nov 12, 2025

Be careful @abussy. I think @antoine-levitt is right (and I unfortunately missed it). The important difference is

 n = count(occ_i -> abs(occ_i) > occupation_threshold, occ)

versus something like

N = [something(findlast(x -> abs(x) > threshold, occk), 0) for occk in occupation]

which has been used so far. Whereas the former fails for Methfessel-Paxton and Marzari-Vanderbilt, the latter will still work.

BTW we should have a test for this kind of edge case. At least not the first time I make this mistake.

@mfherbst
Copy link
Member

mfherbst commented Nov 12, 2025

Edit: In reading your post again, I think you say the same thing, all right then !

Indeed I think it's fine to make the definition:

  • occupied: Occupations can be smaller than threshold in magnitude, but are usually larger
  • non-occupied: Occupations definitely smaller than threshold in magnitude

@abussy
Copy link
Collaborator Author

abussy commented Nov 12, 2025

Yes, we are in agreement here.

I'll open a new PR with a fix. I'll also try and find a new test that breaks the current state.

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 this pull request may close these issues.

3 participants