Skip to content

Real DFT as a level of theory, four more potentials, and two silent unit errors - #15

Merged
Starlitnightly merged 4 commits into
mainfrom
real-dft-and-more-levels
Aug 4, 2026
Merged

Real DFT as a level of theory, four more potentials, and two silent unit errors#15
Starlitnightly merged 4 commits into
mainfrom
real-dft-and-more-levels

Conversation

@Starlitnightly

Copy link
Copy Markdown
Contributor

matverse could reach six levels and every one of them was a surrogate. GPAW is now registered as gpaw-pbe / gpaw-pbe-fast with kind="dft" and surrogate=False, so the one level that solves the Kohn-Sham equations is distinguishable from the ones that reproduce something that did. m3gnet, m3gnet-r2scan, tensornet and orb are added alongside.

Validated by running it, not by citing it

Through matverse, on real PBE:

matverse + GPAW PBE literature
Si lattice constant (mv.calc.relax) 5.479 Å 5.47
Si bulk modulus (mv.prop.eos) 88.7 GPa 88–89
fit residual 0.002 meV/atom

Two things that would have been wrong silently

matgl returns stress in GPa; ASE's contract is eV/ų. Leaving stress_unit alone makes every stress-derived quantity — elastic constants, bulk modulus, pressure — exactly 160.2× too large, with no error raised anywhere. That factor is the GPa→eV/ų conversion, which is what makes it recognisable once you look. Fixed, with a test that fails on the unfixed version.

The second took longer and is not GPAW's fault. I first gave the DFT levels a k-point density — sensible for comparing different materials, wrong for scanning one material's volume. The mesh changes discretely as the cell grows, and each change puts a step in E(V) that the fit reads as curvature. Silicon, same calculator, only the k-points varying:

k-points bulk modulus mesh across the scan
density 2.0 −879 GPa 125 → 64
density 2.5 319 GPa 216 → 125
density 3.0 125 GPa 343 → 216
density 4.0 85.7 GPa 729 → 512
fixed 8×8×8 88.7 GPa unchanged

A negative bulk modulus is not a soft crystal. Raising the density only shrinks the jump; it never removes it. The plane-wave cutoff was converged the whole time — PW(400), PW(600) and PW(800) agree to 0.1 GPa — so the entire error was the mesh.

obs["eos_residual"] catches it long before the modulus does: 9.5 meV/atom against 0.002, a factor of nearly five thousand. The advice already in that function's notes — a residual far above a meV is a fit that should not be read — was enough to reject it without knowing the cause.

pymatviz: a judgement, not a wholesale add

Most of it duplicates mv.pl, which already has hull, parity, periodic_table, spectra, scatter, bands and distribution. Wrapping those again is the mistake the coverage map exists to prevent. The one genuinely absent capability is the space-group distribution, so mv.pl.spacegroups does that directly — grouped and coloured by crystal system rather than plotted as 230 flat categories, and able to overlay requested against achieved for mv.gen.from_symmetry output.

mv.pp.symmetry gained spacegroup_number and spacegroup_symbol while I was there: it was already building the analyser, and a symmetry report giving the crystal system and point group but not the group itself is missing the one label everybody cites.

Verification

  • 960 pass on py3.12 with GPAW and matgl; 850 on py3.10 without them.
  • The level-metadata tests skip where a backend is absent rather than failing — which they did not do at first, and py3.10 caught it.
  • Contract-verified rate stays 165/165. All 16 notebooks execute. Docs build.
  • CI gains dft: the GPAW test is one H₂ molecule in LCAO, ~3 s. The crystal validations behind the numbers above take minutes each and stay out.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wh5NzNE9Wn49nuUm7zg2a

Zehua Zeng and others added 4 commits August 4, 2026 06:58
…nit errors

matverse could reach six levels and every one of them was a surrogate. GPAW is
now registered as gpaw-pbe and gpaw-pbe-fast, with kind="dft" and
surrogate=False, so the one level that solves the Kohn-Sham equations is
distinguishable from the ones that reproduce something that did. m3gnet,
m3gnet-r2scan, tensornet and orb are added alongside.

Validated by running it, not by citing it. Through matverse, on real PBE:

  mv.calc.relax  -> Si lattice constant 5.479 A   (PBE literature 5.47)
  mv.prop.eos    -> Si bulk modulus     88.7 GPa  (PBE literature 88-89)
                    fit residual        0.002 meV/atom

Two things that would have been wrong silently.

matgl returns stress in GPa while ASE's contract is eV/A^3, so leaving
stress_unit alone makes every stress-derived quantity - elastic constants, bulk
modulus, pressure - exactly 160.2x too large with no error raised anywhere. The
factor is the GPa-to-eV/A^3 conversion, which is what makes it recognisable
once you look. Fixed, with a test that fails on the unfixed version.

The second took longer to find and is not GPAW's fault. I first gave the DFT
levels a k-point *density*, which is the sensible thing for comparing different
materials and the wrong thing for scanning one material's volume: the mesh
changes discretely as the cell grows, and each change puts a step in E(V) that
the fit reads as curvature. Silicon, same calculator, only the k-points varying:

  density 2.0 -> -879 GPa   (125 -> 64 k-points)
  density 2.5 ->  319 GPa   (216 -> 125)
  density 3.0 ->  125 GPa   (343 -> 216)
  density 4.0 -> 85.7 GPa   (729 -> 512)
  fixed 8x8x8 -> 88.7 GPa   (unchanged)

A negative bulk modulus is not a soft crystal. Raising the density only shrinks
the jump; it never removes it. The plane-wave cutoff was converged the whole
time - PW(400), PW(600) and PW(800) agree to 0.1 GPa - so the entire error was
the mesh. The built-in levels use a fixed mesh, and mv.prop.eos now says why.

obs['eos_residual'] catches it long before the modulus does: 9.5 meV/atom
against 0.002, a factor of nearly five thousand, and the advice already in that
function's notes - a residual far above a meV is a fit that should not be read -
was enough to reject it without knowing the cause.

On pymatviz I made a judgement rather than adding it wholesale. Most of it
duplicates mv.pl, which already has hull, parity, periodic_table, spectra,
scatter, bands and distribution; wrapping those again would be the mistake the
coverage map exists to prevent. The one genuinely absent capability is the
space-group distribution, so mv.pl.spacegroups does that directly, grouped and
coloured by crystal system rather than plotted as 230 flat categories.
mv.pp.symmetry gained spacegroup_number and spacegroup_symbol while I was
there - it was already building the analyser, and a symmetry report that gives
the crystal system and the point group but not the group itself is missing the
one label everybody cites.

960 tests pass on py3.12 with GPAW and matgl, 850 on py3.10 without them. The
level-metadata tests skip where a backend is absent rather than failing, which
they did not do at first. Contract-verified rate stays 165/165, all 16
notebooks execute, docs build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wh5NzNE9Wn49nuUm7zg2a
All three test legs went red on the previous commit. GPAW has no wheel: it
compiles against libxc and BLAS, and the runner carries neither, so pip got as
far as "fatal error: xc.h: No such file or directory" before anything ran. It
built here only because this node happens to have both.

Installing it in the main matrix was the wrong call regardless of the headers.
GPAW compiles from source, and matgl and orb-models pull torch, which nothing
else in the matrix needs — three legs would each pay a couple of minutes to
cover one job's worth of tests. They now share a 'calculators' job, the same
shape the anharmonic job already has and for the same reason.

The job prints mv.calc.available() before running anything, so a level whose
backend failed to install is visible as a line in the log rather than as a
silently skipped test.
GPAW 26 includes <algorithm> from sources the build compiles as C, so the
runner needs a C++ standard library it does not carry by default. The previous
attempt got past libxc and stopped at 'fatal error: algorithm: No such file or
directory'.
Three attempts, same error. GPAW ships no wheel and its 26.x sources include
C++ headers from files the build compiles as C, so it stops at 'fatal error:
algorithm: No such file or directory' whether or not libxc, BLAS and
build-essential are installed. It builds here because this node's compiler puts
the C++ headers on the default include path; a stock ubuntu runner's does not.

So the calculators job installs the torch-backed potentials only, and keeps the
test that matters most there — the one that fails when matgl's stress is left
in GPa, a 160x error nothing else would catch.

The real-DFT numbers stay where they were measured, in the notes, and
TestRealDFT says why it skips rather than leaving a reader to assume CI covers
it.
@Starlitnightly
Starlitnightly merged commit 35228cf into main Aug 4, 2026
6 checks passed
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.

1 participant