Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions src/qe_tools/outputs/parsers/pw.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def parse(content):
r"([\-\d.E+]+)\s+([\-\d.E+]+)"
)
_HOMO_RE = re.compile(r"highest occupied level\s*\(ev\):\s*([\-\d.E+]+)")
_TOTAL_ENERGY_RE = re.compile(r"!\s+total energy\s*=\s*([\-\d.E+]+)\s*Ry")


class PwStdoutParser(BaseStdoutParser):
Expand All @@ -78,4 +79,10 @@ def parse(content: str) -> dict:
if match:
parsed_data["highest_occupied_level"] = float(match.group(1))

# Final SCF total energy in Ry. For relax/md runs QE prints one `!` line
# per ionic step; the final converged value is the last one.
energy_matches = _TOTAL_ENERGY_RE.findall(content)
if energy_matches:
parsed_data["total_energy"] = float(energy_matches[-1])

return parsed_data
18 changes: 14 additions & 4 deletions src/qe_tools/outputs/pw.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Annotated, TextIO

import numpy as np
from glom import Coalesce, Spec
from glom import Check, Coalesce, Spec

from dough import Unit
from dough.converters import BaseConverter
Expand Down Expand Up @@ -324,9 +324,19 @@ class _PwMapping:
total_energy: Annotated[
float,
Spec(
(
"xml.output.total_energy.etot",
lambda energy: energy * CONSTANTS.hartree_to_ev,
Coalesce(
(
# For `nscf` and `bands` QE never assigns `etot` and writes
# `<etot>0.0</etot>` (see PW/src/non_scf.f90 and
# PW/src/pw_restart_new.f90); fall through to stdout below.
Check(
"xml.input.control_variables.calculation",
one_of=("scf", "relax", "vc-relax", "md", "vc-md"),
),
"xml.output.total_energy.etot",
lambda energy: energy * CONSTANTS.hartree_to_ev,
),
("stdout.total_energy", lambda energy: energy * CONSTANTS.ry_to_ev),
)
),
Unit("eV"),
Expand Down
236 changes: 236 additions & 0 deletions tests/outputs/fixtures/pw/nscf_etot_clobber/data-file-schema.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
<?xml version="1.0" encoding="UTF-8"?>
<qes:espresso xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:qes="http://www.quantum-espresso.org/ns/qes/qes-1.0" xsi:schemaLocation="http://www.quantum-espresso.org/ns/qes/qes-1.0 http://www.quantum-espresso.org/ns/qes/qes_250521.xsd" Units="Hartree atomic units">
<!-- All quantities are in Hartree atomic units unless otherwise specified -->
<general_info>
<xml_format NAME="QEXSD" VERSION="25.05.21">QEXSD_25.05.21</xml_format>
<creator NAME="PWSCF" VERSION="7.5">XML file generated by PWSCF</creator>
<created DATE=" 7May2026" TIME="16:24: 5">This run was terminated on: 16:24: 5 7 May 2026</created>
<job></job>
</general_info>
<parallel_info>
<nprocs>1</nprocs>
<nthreads>1</nthreads>
<ntasks>1</ntasks>
<nbgrp>1</nbgrp>
<npool>1</npool>
<ndiag>1</ndiag>
</parallel_info>
<input>
<control_variables>
<title></title>
<calculation>nscf</calculation>
<restart_mode>from_scratch</restart_mode>
<prefix>MgO</prefix>
<pseudo_dir>../PP</pseudo_dir>
<outdir>../tmp</outdir>
<stress>false</stress>
<forces>false</forces>
<wf_collect>true</wf_collect>
<disk_io>low</disk_io>
<max_seconds>10000000</max_seconds>
<nstep>1</nstep>
<etot_conv_thr>5.000000000000000E-005</etot_conv_thr>
<forc_conv_thr>5.000000000000000E-004</forc_conv_thr>
<press_conv_thr>5.000000000000000E-001</press_conv_thr>
<verbosity>low</verbosity>
<print_every>100000</print_every>
<fcp>false</fcp>
<rism>false</rism>
</control_variables>
<atomic_species ntyp="2">
<species name="Mg">
<mass>2.403500000000000E+001</mass>
<pseudo_file>Mg.pbe-n-kjpaw_psl.0.3.0.UPF</pseudo_file>
</species>
<species name="O">
<mass>1.599940000000000E+001</mass>
<pseudo_file>O.pbe-n-kjpaw_psl.0.1.UPF</pseudo_file>
</species>
</atomic_species>
<atomic_structure nat="2" alat="8.0374557182000004" bravais_index="2">
<atomic_positions>
<atom name="Mg" index="1">0.000000000000000E+000 0.000000000000000E+000 0.000000000000000E+000</atom>
<atom name="O" index="2">4.018727859100000E+000 4.018727859100000E+000 4.018727859100000E+000</atom>
</atomic_positions>
<cell>
<a1>-4.018727859100000E+000 0.000000000000000E+000 4.018727859100000E+000</a1>
<a2>0.000000000000000E+000 4.018727859100000E+000 4.018727859100000E+000</a2>
<a3>-4.018727859100000E+000 4.018727859100000E+000 0.000000000000000E+000</a3>
</cell>
</atomic_structure>
<dft>
<functional>PBE</functional>
</dft>
<spin>
<lsda>false</lsda>
<noncolin>false</noncolin>
<spinorbit>false</spinorbit>
</spin>
<bands>
<nbnd>8</nbnd>
<tot_charge>0.000000000000000E+000</tot_charge>
<occupations>tetrahedra</occupations>
</bands>
<basis>
<gamma_only>false</gamma_only>
<ecutwfc>2.500000000000000E+001</ecutwfc>
<ecutrho>2.000000000000000E+002</ecutrho>
</basis>
<electron_control>
<diagonalization>davidson</diagonalization>
<mixing_mode>plain</mixing_mode>
<mixing_beta>7.000000000000000E-001</mixing_beta>
<conv_thr>5.000000000000000E-007</conv_thr>
<mixing_ndim>8</mixing_ndim>
<max_nstep>100</max_nstep>
<exx_nstep>100</exx_nstep>
<real_space_q>false</real_space_q>
<real_space_beta>false</real_space_beta>
<tq_smoothing>false</tq_smoothing>
<tbeta_smoothing>false</tbeta_smoothing>
<diago_thr_init>0.000000000000000E+000</diago_thr_init>
<diago_full_acc>false</diago_full_acc>
<diago_cg_maxiter>20</diago_cg_maxiter>
<diago_rmm_ndim>4</diago_rmm_ndim>
<diago_gs_nblock>16</diago_gs_nblock>
<diago_rmm_conv>false</diago_rmm_conv>
</electron_control>
<k_points_IBZ>
<monkhorst_pack nk1="16" nk2="16" nk3="16" k1="0" k2="0" k3="0">Monkhorst-Pack</monkhorst_pack>
</k_points_IBZ>
<ion_control>
<ion_dynamics>none</ion_dynamics>
<upscale>1.000000000000000E+002</upscale>
<remove_rigid_rot>false</remove_rigid_rot>
<refold_pos>false</refold_pos>
</ion_control>
<cell_control>
<cell_dynamics>none</cell_dynamics>
<pressure>0.000000000000000E+000</pressure>
<wmass>0.000000000000000E+000</wmass>
<cell_do_free>all</cell_do_free>
</cell_control>
<symmetry_flags>
<nosym>false</nosym>
<nosym_evc>false</nosym_evc>
<noinv>false</noinv>
<no_t_rev>false</no_t_rev>
<force_symmorphic>false</force_symmorphic>
<use_all_frac>false</use_all_frac>
</symmetry_flags>
<free_positions rank="2" dims=" 3 2">
1 1 1
1 1 1
</free_positions>
<twoch_>
<twochem>false</twochem>
<nbnd_cond>0</nbnd_cond>
<degauss_cond>0.000000000000000E+000</degauss_cond>
<nelec_cond>0.000000000000000E+000</nelec_cond>
</twoch_>
</input>
<output>
<convergence_info>
<scf_conv>
<convergence_achieved>false</convergence_achieved>
<n_scf_steps>1</n_scf_steps>
<scf_error>0.000000000000000E+000</scf_error>
</scf_conv>
<wf_collected>true</wf_collected>
</convergence_info>
<algorithmic_info>
<real_space_q>false</real_space_q>
<real_space_beta>false</real_space_beta>
<uspp>true</uspp>
<paw>true</paw>
</algorithmic_info>
<atomic_species ntyp="2" pseudo_dir="../PP/">
<species name="Mg">
<mass>2.403500000000000E+001</mass>
<pseudo_file>Mg.pbe-n-kjpaw_psl.0.3.0.UPF</pseudo_file>
</species>
<species name="O">
<mass>1.599940000000000E+001</mass>
<pseudo_file>O.pbe-n-kjpaw_psl.0.1.UPF</pseudo_file>
</species>
</atomic_species>
<atomic_structure nat="2" num_of_atomic_wfc="8" alat="8.0374557182000004" bravais_index="2">
<atomic_positions>
<atom name="Mg" index="1">
0.000000000000000E+000 0.000000000000000E+000 0.000000000000000E+000
</atom>
<atom name="O" index="2">4.018727859100000E+000 4.018727859100000E+000 4.018727859100000E+000</atom>
</atomic_positions>
<cell>
<a1>-4.018727859100000E+000 0.000000000000000E+000 4.018727859100000E+000</a1>
<a2>0.000000000000000E+000 4.018727859100000E+000 4.018727859100000E+000</a2>
<a3>-4.018727859100000E+000 4.018727859100000E+000 0.000000000000000E+000</a3>
</cell>
</atomic_structure>
<basis_set>
<gamma_only>false</gamma_only>
<ecutwfc>2.500000000000000E+001</ecutwfc>
<ecutrho>2.000000000000000E+002</ecutrho>
<fft_grid nr1="40" nr2="40" nr3="40"></fft_grid>
<fft_smooth nr1="25" nr2="25" nr3="25"></fft_smooth>
<fft_box nr1="40" nr2="40" nr3="40"></fft_box>
<ngm>17477</ngm>
<ngms>6183</ngms>
<npwx>790</npwx>
<reciprocal_lattice>
<b1>
-1.000000000000000E+000 -1.000000000000000E+000 1.000000000000000E+000
</b1>
<b2>1.000000000000000E+000 1.000000000000000E+000 1.000000000000000E+000</b2>
<b3>-1.000000000000000E+000 1.000000000000000E+000 -1.000000000000000E+000</b3>
</reciprocal_lattice>
</basis_set>
<dft>
<functional>PBE</functional>
</dft>
<magnetization>
<lsda>false</lsda>
<noncolin>false</noncolin>
<spinorbit>false</spinorbit>
<absolute>0.000000000000000E+000</absolute>
</magnetization>
<total_energy>
<etot>0.000000000000000E+000</etot>
<eband>-2.412528325153409E-002</eband>
<ehart>5.173955844516139E+000</ehart>
<vtxc>-5.381547635132835E+000</vtxc>
<etxc>-4.655081273351107E+000</etxc>
<ewald>0.000000000000000E+000</ewald>
</total_energy>
<band_structure>
<lsda>false</lsda>
<noncolin>false</noncolin>
<spinorbit>false</spinorbit>
<nbnd>8</nbnd>
<nelec>8.000000000000000E+000</nelec>
<fermi_energy>2.208720474420502E-001</fermi_energy>
<starting_k_points>
<monkhorst_pack nk1="16" nk2="16" nk3="16" k1="0" k2="0" k3="0">Monkhorst-Pack</monkhorst_pack>
</starting_k_points>
<nks>145</nks>
<occupations_kind>tetrahedra</occupations_kind>
<ks_energies>
<k_point weight="4.8828125000000000E-004">0.000000000000000E+000 0.000000000000000E+000 0.000000000000000E+000</k_point>
<npw>749</npw>
<eigenvalues size="8">
-4.333761183969641E-001 1.964059244895351E-001 1.964059244907791E-001
1.964059244947730E-001 3.597282634048695E-001
7.717057859626861E-001 7.717057860499605E-001 7.717057861123908E-001
</eigenvalues>
<occupations size="8">
9.999999999999996E-001 9.999999999999996E-001 9.999999999999996E-001
9.999999999999996E-001 0.000000000000000E+000
0.000000000000000E+000 0.000000000000000E+000 0.000000000000000E+000
</occupations>
</ks_energies>
</band_structure>
</output>
<exit_status>0</exit_status>
<cputime>0</cputime>
<closed DATE=" 7 May 2026" TIME="16:24: 5"></closed>
</qes:espresso>
12 changes: 12 additions & 0 deletions tests/outputs/fixtures/pw/nscf_etot_clobber/scf.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Program PWSCF v.7.5 starts on 7May2026 at 16:23:36

--- Removed: SCF cycle, eigenvalues, k-points; only keep header + final energies + JOB DONE. ---

highest occupied level (ev): 5.3452

! total energy = -75.53725762 Ry
total all-electron energy = -551.438359 Ry

=------------------------------------------------------------------------------=
JOB DONE.
=------------------------------------------------------------------------------=
26 changes: 26 additions & 0 deletions tests/outputs/test_pw_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,32 @@ def test_failed(data_regression, to_jsonable, fixture_directory):
)


def test_total_energy_nscf_clobber():
"""Test that `total_energy` falls back to stdout when an nscf XML clobbered the scf one.

QE writes `<etot>0.0</etot>` for `nscf` and `bands` runs (see PW/src/non_scf.f90).
In the common `scf → nscf` workflow at a shared `prefix`, the nscf run overwrites
the scf XML on disk, so the only reliable source of the SCF total energy is the
`! total energy = ...` line in the scf stdout.
"""
from qe_tools import CONSTANTS

pw_directory = Path(__file__).parent / "fixtures" / "pw" / "nscf_etot_clobber"

pw_out = PwOutput.from_dir(pw_directory)

# Sanity check: the fixture really is an nscf XML with the bogus etot.
assert (
pw_out.raw_outputs["xml"]["input"]["control_variables"]["calculation"] == "nscf"
)
assert pw_out.raw_outputs["xml"]["output"]["total_energy"]["etot"] == 0.0

# Despite that, `total_energy` should resolve via the stdout fallback.
assert pw_out.get_output("total_energy") == pytest.approx(
-75.53725762 * CONSTANTS.ry_to_ev
)


def test_insulator_homo(robust_data_regression_check):
"""Test stdout-derived `highest_occupied_level` from an insulator SCF."""

Expand Down
1 change: 1 addition & 0 deletions tests/outputs/test_pw_output/test_default_xml_211101_.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ base_outputs:
raw_outputs:
stdout:
code_version: '7.0'
total_energy: -22.13271119
wall_time_seconds: 1.96
xml:
'@Units': Hartree atomic units
Expand Down
1 change: 1 addition & 0 deletions tests/outputs/test_pw_output/test_default_xml_220603_.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ base_outputs:
raw_outputs:
stdout:
code_version: '7.1'
total_energy: -22.65373597
wall_time_seconds: 2.02
xml:
'@Units': Hartree atomic units
Expand Down
1 change: 1 addition & 0 deletions tests/outputs/test_pw_output/test_default_xml_230310_.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ raw_outputs:
stdout:
code_version: '7.2'
highest_occupied_level: 9.0922
total_energy: -396.09758618
wall_time_seconds: 100.44
xml:
'@Units': Hartree atomic units
Expand Down
1 change: 1 addition & 0 deletions tests/outputs/test_pw_output/test_default_xml_240411_.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ base_outputs:
raw_outputs:
stdout:
code_version: 7.3.1
total_energy: -22.66279398
wall_time_seconds: 2.51
xml:
'@Units': Hartree atomic units
Expand Down
1 change: 1 addition & 0 deletions tests/outputs/test_pw_output/test_default_xml_250521_.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ base_outputs:
raw_outputs:
stdout:
code_version: '7.5'
total_energy: -252.68718165
wall_time_seconds: 2.33
xml:
'@Units': Hartree atomic units
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,4 @@ base_outputs:
- F
- Li
- F
total_energy: 0.0
total_magnetization: 0.0
Loading