Could a bookkeeping bug make DFT seem to contradict the atomistic model?
Yes. Energies were divided by the atom count twice, shrinking a real 100 meV/atom gap to 6; it was caught before any result was used.
In the log: A factor of sixteen that would have read as a physics result
recordedDate not stated in the log; it was written between the commit of 2026-09-13 08:16 and the first commit that contains it, 2026-09-16 02:04rung 4 · DFT0 predictions · 1 result paragraphEXPERIMENTS.md lines 4136–4188
What E76 did and how it came out, drawn from this record and the files it names (book/assets/diagrams/exp/E76.svg).
Results
EXPERIMENTS.md · line 4136
E76 — A factor of sixteen that would have read as a physics result
parse_qe_output divides the Quantum ESPRESSO total energy by the atom count before
returning it. Both callers divided again: scripts/dft_check.py when it recorded a row,
and the new Verifier.with_dft when it built one, because the second was written from the
first.
Why this one was dangerous. An absolute energy sixteen times too small is obvious -
-358 eV/atom is not a number anyone believes. But the rung reports a difference, and the
difference is scaled by the same factor. A real 100 meV/atom gap between a candidate and
MoNbTaW arrives as 6. Set beside MACE's 100, the output reads:
which is not an implausible number. It is exactly the shape of the finding the DFT rung was
built to look for - first principles contradicting the potential the whole project rests on
and it would have been believed.
Found before the run finished, by parsing completed output rather than trusting the
path. Four finished pw.out files from the k-point study were run through
parse_qe_output: all four returned about -5736 eV/atom for a 16-atom cell whose total is
-6745.78 Ry, which is the total divided by sixteen already. The fifth, still running,
raised rather than returning a partial energy, which is the resume guard behaving.
A regression test now pins the parse to eV/atom against a known total. The rule this came
from is the reason it was caught: the check on a path is worth running before the thing that
depends on it produces a number.
A second defect on the same path, found the same way.configs/rhea_a100.json asks for
mpirun -np 6, and neither caller read it: both invoked pw.x directly, so every DFT run
in this project has been a single MPI rank. Quantum ESPRESSO says so in its own header -
Parallel version (MPI & OpenMP) above Number of MPI processes: 1 - and the header had
never been read either. forager/evaluators.py::qe_command now builds the launcher the
config names, substituting the resolved path for the bare pw.x, and falls back to a plain
invocation when the launcher is absent, so a missing mpirun slows a run rather than
failing it. A config field that is set and ignored is worse than one that is missing,
because it reads as a decision somebody made.
And a third on the same path, which the first fix exposed. Honouring mpirun -np 6
made Quantum ESPRESSO report Number of MPI processes: 6 above Threads/MPI process: 12 -
seventy-two threads on a twelve-core machine, which is slower than the single rank it
replaced, not faster. omp_threads: 2 sat in the config beside command, unread in the
same way. qe_env now exports it, and the header reads 6 ranks at 2 threads on 12 cores.
All three defects were on the same short path, all three were visible in output the project
had already produced, and none had been read: the parse returning eV/atom, the launcher
field, and the thread count in every QE header ever written here.
The hour this cost, measured. The first reference run was killed by its own 3600 s
timeout with nothing on disk - capture_output=True held the whole SCF in a pipe, so
the timeout destroyed it rather than truncating it. That is the checkpoint rule, on a path
that had not been given one. Output now streams to pw.out as it is produced, so the run
can be watched while it runs and survives being killed.
The full record
EXPERIMENTS.md · lines 4136–4188
E76 — A factor of sixteen that would have read as a physics result
parse_qe_output divides the Quantum ESPRESSO total energy by the atom count before
returning it. Both callers divided again: scripts/dft_check.py when it recorded a row,
and the new Verifier.with_dft when it built one, because the second was written from the
first.
Why this one was dangerous. An absolute energy sixteen times too small is obvious -
-358 eV/atom is not a number anyone believes. But the rung reports a difference, and the
difference is scaled by the same factor. A real 100 meV/atom gap between a candidate and
MoNbTaW arrives as 6. Set beside MACE's 100, the output reads:
which is not an implausible number. It is exactly the shape of the finding the DFT rung was
built to look for - first principles contradicting the potential the whole project rests on
and it would have been believed.
Found before the run finished, by parsing completed output rather than trusting the
path. Four finished pw.out files from the k-point study were run through
parse_qe_output: all four returned about -5736 eV/atom for a 16-atom cell whose total is
-6745.78 Ry, which is the total divided by sixteen already. The fifth, still running,
raised rather than returning a partial energy, which is the resume guard behaving.
A regression test now pins the parse to eV/atom against a known total. The rule this came
from is the reason it was caught: the check on a path is worth running before the thing that
depends on it produces a number.
A second defect on the same path, found the same way.configs/rhea_a100.json asks for
mpirun -np 6, and neither caller read it: both invoked pw.x directly, so every DFT run
in this project has been a single MPI rank. Quantum ESPRESSO says so in its own header -
Parallel version (MPI & OpenMP) above Number of MPI processes: 1 - and the header had
never been read either. forager/evaluators.py::qe_command now builds the launcher the
config names, substituting the resolved path for the bare pw.x, and falls back to a plain
invocation when the launcher is absent, so a missing mpirun slows a run rather than
failing it. A config field that is set and ignored is worse than one that is missing,
because it reads as a decision somebody made.
And a third on the same path, which the first fix exposed. Honouring mpirun -np 6
made Quantum ESPRESSO report Number of MPI processes: 6 above Threads/MPI process: 12 -
seventy-two threads on a twelve-core machine, which is slower than the single rank it
replaced, not faster. omp_threads: 2 sat in the config beside command, unread in the
same way. qe_env now exports it, and the header reads 6 ranks at 2 threads on 12 cores.
All three defects were on the same short path, all three were visible in output the project
had already produced, and none had been read: the parse returning eV/atom, the launcher
field, and the thread count in every QE header ever written here.
The hour this cost, measured. The first reference run was killed by its own 3600 s
timeout with nothing on disk - capture_output=True held the whole SCF in a pipe, so
the timeout destroyed it rather than truncating it. That is the checkpoint rule, on a path
that had not been given one. Output now streams to pw.out as it is produced, so the run
can be watched while it runs and survives being killed.
Files it names
configs/rhea_a100.json
Paths in the Forager repository, as the log wrote them.