Skip to content

Commit 75c66b5

Browse files
Merge pull request #24 from AsymmetryChou/etot_add
improves the robustness of VASP OUTCAR energy extraction
2 parents dd56aa1 + 317869d commit 75c66b5

2 files changed

Lines changed: 92 additions & 1 deletion

File tree

dftio/io/vasp/vasp_parser.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,35 @@ def get_etot(self, idx):
9191
return self.get_total_energy(idx)
9292

9393
@staticmethod
94-
def read_total_energy(file):
94+
def read_total_energy(file="OUTCAR")->np.float64:
9595
"""
9696
Extract energy(sigma->0) from VASP OUTCAR file.
9797
This is the extrapolated energy to 0K.
98+
99+
For unsuccessful runs, a warning will be logged. Although the energy
100+
may still be extracted, its reliability is not guaranteed.
101+
------------------------------------------
102+
Args:
103+
file (str): Path to the VASP OUTCAR file.
104+
Returns:
105+
energy (np.float64): The extracted total energy(sigma->0).
106+
------------------------------------------
98107
"""
108+
success_completion = False
99109
energy = []
100110
with open(file, 'r') as f:
101111
data = f.readlines()
102112
for line in data:
113+
if 'Voluntary context switches' in line:
114+
success_completion = True
103115
if "energy(sigma->0)" in line:
104116
energy.append(float(re.findall(r'[\-\d\.E]+', line)[-1]))
105117
if len(energy) > 1:
106118
log.warning("Multiple energy(sigma->0) found in OUTCAR. Using the last one.")
107119
energy = energy[-1] if energy else None
108120
assert energy is not None, "Cannot find energy(sigma->0) in OUTCAR."
121+
if not success_completion:
122+
log.warning(f"WARNING!!! {file} does not indicate successful completion.")
109123

110124
energy = np.array(energy, dtype=np.float64)
111125
return energy

test/test_vasp_energy.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,87 @@ def test_vasp_multiple_energies_warning():
181181
logger.removeHandler(handler)
182182

183183

184+
def test_vasp_unsuccessful_completion_warning():
185+
"""Test that warning is logged when OUTCAR does not indicate successful completion."""
186+
import tempfile
187+
import logging
188+
from io import StringIO
189+
190+
# Create a temporary OUTCAR without "Voluntary context switches"
191+
with tempfile.TemporaryDirectory() as tmpdir:
192+
test_outcar = os.path.join(tmpdir, "OUTCAR")
193+
with open(test_outcar, 'w') as f:
194+
f.write(" energy without entropy= -10.53 energy(sigma->0) = -10.53\n")
195+
# No "Voluntary context switches" line - simulates unsuccessful completion
196+
197+
# Capture log output
198+
log_stream = StringIO()
199+
handler = logging.StreamHandler(log_stream)
200+
logger = logging.getLogger('dftio.io.vasp.vasp_parser')
201+
logger.addHandler(handler)
202+
logger.setLevel(logging.WARNING)
203+
204+
try:
205+
energy = VASPParser.read_total_energy(test_outcar)
206+
207+
# Should still extract energy
208+
assert np.isclose(energy, -10.53, atol=1e-5), \
209+
f"Expected energy -10.53, got {energy}"
210+
211+
# Check if warning was logged about unsuccessful completion
212+
log_contents = log_stream.getvalue()
213+
assert "does not indicate successful completion" in log_contents, \
214+
"Warning about unsuccessful completion should be logged"
215+
216+
print(f"Unsuccessful completion warning test passed")
217+
finally:
218+
logger.removeHandler(handler)
219+
220+
221+
def test_vasp_successful_completion_no_warning():
222+
"""Test that no warning is logged when OUTCAR indicates successful completion."""
223+
import tempfile
224+
import logging
225+
from io import StringIO
226+
227+
# Create a temporary OUTCAR with "Voluntary context switches"
228+
with tempfile.TemporaryDirectory() as tmpdir:
229+
test_outcar = os.path.join(tmpdir, "OUTCAR")
230+
with open(test_outcar, 'w') as f:
231+
f.write(" energy without entropy= -10.53 energy(sigma->0) = -10.53\n")
232+
f.write(" Voluntary context switches: 1234\n") # Indicates successful completion
233+
234+
# Capture log output
235+
log_stream = StringIO()
236+
handler = logging.StreamHandler(log_stream)
237+
logger = logging.getLogger('dftio.io.vasp.vasp_parser')
238+
logger.addHandler(handler)
239+
logger.setLevel(logging.WARNING)
240+
241+
try:
242+
energy = VASPParser.read_total_energy(test_outcar)
243+
244+
# Should extract energy
245+
assert np.isclose(energy, -10.53, atol=1e-5), \
246+
f"Expected energy -10.53, got {energy}"
247+
248+
# Check that no warning about unsuccessful completion was logged
249+
log_contents = log_stream.getvalue()
250+
assert "does not indicate successful completion" not in log_contents, \
251+
"No warning about unsuccessful completion should be logged for successful runs"
252+
253+
print(f"Successful completion (no warning) test passed")
254+
finally:
255+
logger.removeHandler(handler)
256+
257+
258+
184259
if __name__ == "__main__":
185260
test_vasp_scf_energy()
186261
test_vasp_read_total_energy_static()
187262
test_vasp_energy_write_dat()
188263
test_vasp_energy_write_lmdb()
189264
test_vasp_multiple_energies_warning()
265+
test_vasp_unsuccessful_completion_warning()
266+
test_vasp_successful_completion_no_warning()
190267
print("\nAll VASP energy extraction tests passed!")

0 commit comments

Comments
 (0)