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
16 changes: 15 additions & 1 deletion dftio/io/vasp/vasp_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,35 @@ def get_etot(self, idx):
return self.get_total_energy(idx)

@staticmethod
def read_total_energy(file):
def read_total_energy(file="OUTCAR")->np.float64:
"""
Extract energy(sigma->0) from VASP OUTCAR file.
This is the extrapolated energy to 0K.

For unsuccessful runs, a warning will be logged. Although the energy
may still be extracted, its reliability is not guaranteed.
------------------------------------------
Args:
file (str): Path to the VASP OUTCAR file.
Returns:
energy (np.float64): The extracted total energy(sigma->0).
------------------------------------------
"""
success_completion = False
energy = []
with open(file, 'r') as f:
data = f.readlines()
for line in data:
if 'Voluntary context switches' in line:
success_completion = True
Comment on lines +108 to +114
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Verify the reliability of "Voluntary context switches" as a completion indicator.

The implementation uses "Voluntary context switches" as the indicator for successful VASP completion. Please confirm this is a reliable and documented indicator in VASP OUTCAR files.


🌐 Web query:

Is "Voluntary context switches" a reliable indicator of successful completion in VASP OUTCAR files?

💡 Result:

Short answer: It's a useful but not foolproof heuristic — the presence of the final "Voluntary context switches" (the last line of the VASP timing/accounting block) usually means VASP reached its normal end and printed the final accounting, and many scripts rely on it to mark a finished run. However, it is not guaranteed: some abnormal terminations may omit the accounting block (so the line will be absent even for failed jobs), and other checks (e.g., explicit convergence messages in OUTCAR/OSZICAR, existence/contents of CONTCAR/CHGCAR/OSZICAR, "General timing and accounting"/Total CPU time used) are recommended for robust detection. See examples in storq and common VASP utilities that use this line but also note they treat it as a heuristic. [1][2]

Sources:

  • storq reader: read_outcar_validity uses "Voluntary context switches" to mark proper finish and notes cancelled/error runs may not print it. [1]
  • ASE/Jasp and community examples showing many tools/scripts check the OUTCAR tail for "Voluntary context switches" as a finished-job indicator (but other checks are commonly used). [2]

Add more robust completion detection beyond "Voluntary context switches" line.

The "Voluntary context switches" line is a commonly-used heuristic in VASP tools (storq, ASE/Jasp), but it's not reliable for all cases. Abnormal terminations may omit the accounting block entirely, and some failed jobs may still contain this line.

Improve robustness by checking additional signals: explicit convergence messages in OUTCAR/OSZICAR, existence/contents of CONTCAR/CHGCAR/OSZICAR, or "General timing and accounting"/Total CPU time. At minimum, add a comment noting this is a heuristic and document the limitations.

🤖 Prompt for AI Agents
In dftio/io/vasp/vasp_parser.py around lines 108 to 114, the current completion
detection relies solely on the "Voluntary context switches" line which is a
heuristic and can be false-positive/negative; update the logic to be more robust
by: keep the existing check but also scan the OUTCAR/OSZICAR contents for
explicit convergence/termination phrases (e.g., "reached required accuracy",
"reached required accuracy - stopping structural energy minimisation", "General
timing and accounting", "Total CPU time"), verify existence and non-empty status
of key output files (CONTCAR, OSZICAR, CHGCAR) as additional success signals,
and treat completion as true only if one or more of these indicators are
present; if full checks are not implemented, add a clear inline comment
describing this heuristic and its limitations.

if "energy(sigma->0)" in line:
energy.append(float(re.findall(r'[\-\d\.E]+', line)[-1]))
if len(energy) > 1:
log.warning("Multiple energy(sigma->0) found in OUTCAR. Using the last one.")
energy = energy[-1] if energy else None
assert energy is not None, "Cannot find energy(sigma->0) in OUTCAR."
if not success_completion:
log.warning(f"WARNING!!! {file} does not indicate successful completion.")

energy = np.array(energy, dtype=np.float64)
return energy
77 changes: 77 additions & 0 deletions test/test_vasp_energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,87 @@ def test_vasp_multiple_energies_warning():
logger.removeHandler(handler)


def test_vasp_unsuccessful_completion_warning():
"""Test that warning is logged when OUTCAR does not indicate successful completion."""
import tempfile
import logging
from io import StringIO

# Create a temporary OUTCAR without "Voluntary context switches"
with tempfile.TemporaryDirectory() as tmpdir:
test_outcar = os.path.join(tmpdir, "OUTCAR")
with open(test_outcar, 'w') as f:
f.write(" energy without entropy= -10.53 energy(sigma->0) = -10.53\n")
# No "Voluntary context switches" line - simulates unsuccessful completion

# Capture log output
log_stream = StringIO()
handler = logging.StreamHandler(log_stream)
logger = logging.getLogger('dftio.io.vasp.vasp_parser')
logger.addHandler(handler)
logger.setLevel(logging.WARNING)

try:
energy = VASPParser.read_total_energy(test_outcar)

# Should still extract energy
assert np.isclose(energy, -10.53, atol=1e-5), \
f"Expected energy -10.53, got {energy}"

# Check if warning was logged about unsuccessful completion
log_contents = log_stream.getvalue()
assert "does not indicate successful completion" in log_contents, \
"Warning about unsuccessful completion should be logged"

print(f"Unsuccessful completion warning test passed")
finally:
logger.removeHandler(handler)


def test_vasp_successful_completion_no_warning():
"""Test that no warning is logged when OUTCAR indicates successful completion."""
import tempfile
import logging
from io import StringIO

# Create a temporary OUTCAR with "Voluntary context switches"
with tempfile.TemporaryDirectory() as tmpdir:
test_outcar = os.path.join(tmpdir, "OUTCAR")
with open(test_outcar, 'w') as f:
f.write(" energy without entropy= -10.53 energy(sigma->0) = -10.53\n")
f.write(" Voluntary context switches: 1234\n") # Indicates successful completion

# Capture log output
log_stream = StringIO()
handler = logging.StreamHandler(log_stream)
logger = logging.getLogger('dftio.io.vasp.vasp_parser')
logger.addHandler(handler)
logger.setLevel(logging.WARNING)

try:
energy = VASPParser.read_total_energy(test_outcar)

# Should extract energy
assert np.isclose(energy, -10.53, atol=1e-5), \
f"Expected energy -10.53, got {energy}"

# Check that no warning about unsuccessful completion was logged
log_contents = log_stream.getvalue()
assert "does not indicate successful completion" not in log_contents, \
"No warning about unsuccessful completion should be logged for successful runs"

print(f"Successful completion (no warning) test passed")
finally:
logger.removeHandler(handler)



if __name__ == "__main__":
test_vasp_scf_energy()
test_vasp_read_total_energy_static()
test_vasp_energy_write_dat()
test_vasp_energy_write_lmdb()
test_vasp_multiple_energies_warning()
test_vasp_unsuccessful_completion_warning()
test_vasp_successful_completion_no_warning()
print("\nAll VASP energy extraction tests passed!")
Loading