Reading VASP output files

httk-atomistic ships string-preserving readers for the VASP output files under httk.atomistic.integrations.vasp.io. The I/O layer does not parse numbers into floats; numeric lexemes are kept as strings, and counts and iteration indices are the small structural integer exceptions. The domain-level VASPStructure and VASPTrajectory wrappers that build on these readers are covered in Integrations.

POSCAR and CONTCAR

read_poscar accepts a path, text stream, or line iterable and returns a neutral vasp-poscar mapping (see Reading VASP POSCAR / CONTCAR files for the full field-by-field description). Path and stream reads include raw, the original decompressed text, when it is available; line iterables set raw to None. Saving a payload with raw writes that text verbatim, so it takes precedence over edited tokens and preserves CRLF as well as other formatting:

import httk.core
from httk.atomistic.integrations.vasp.io import read_poscar

text = "c\r\n1.0\r\n1 0 0\r\n0 1 0\r\n0 0 1\r\nHe\r\n1\r\nDirect\r\n0 0 0\r\n"
payload = read_poscar(text.splitlines(keepends=True))
assert payload["raw"] is None

payload = read_poscar(text.splitlines(keepends=True))
assert payload["coords"] == [["0", "0", "0"]]

For byte-exact save-back, load a filename (including a compressed filename) with raw=True, then save the returned mapping:

from pathlib import Path

source = Path("POSCAR")
source.write_bytes(text.encode())
payload = httk.core.load(str(source), raw=True)
payload["coords"][0] = ["9", "9", "9"]  # raw still wins
httk.core.save(payload, "POSCAR.copy")
assert Path("POSCAR.copy").read_bytes() == source.read_bytes()

OUTCAR

read_outcar returns an OutcarFile. Construction checks existence only. It accepts compressed paths and reopens a fresh forward text stream for each scan; close() is therefore a lifecycle symmetry and owns no persistent handle. Its path property returns the source filename string. The first access to version, parameters, XC, or POTCAR titles performs a bounded prologue scan. The first access to finals, completion, issues, frames, stresses, or elastic moduli performs one full pass and caches only bounded results. The prologue also exposes ions_per_type when VASP prints it.

frames() streams complete ionic frames. Each OutcarFrame retains cell, positions, forces, stress, energies, and MD temperature lexemes. nframes and last_frame are cached by the full pass; frame(i) re-streams the file and is O(file). The full pass also caches magnetization, the per-ion total moments from the final collinear magnetization (x) block (None when the run prints none), and the noncollinear_magnetization flag, set when a (y) or (z) block follows. stresses() returns every six-token in kB row. Elastic tables are available as ElasticModuliBlock values for the TOTAL ELASTIC MODULI, SYMMETRIZED ELASTIC MODULI, and ionic-relaxation headings.

VASP writes stress as [xx, yy, zz, xy, yz, zx] in kBar with compression positive. stress_gpa_voigt() multiplies by 0.1, flips the sign to make tension positive, and returns [xx, yy, zz, yz, xz, xy]:

from httk.atomistic.integrations.vasp.io import OutcarFrame

frame = OutcarFrame(0, None, None, None, ("1", "2", "3", "4", "5", "6"), None, None, None, None)
assert frame.stress_gpa_voigt() == (-0.1, -0.2, -0.30000000000000004, -0.5, -0.6000000000000001, -0.4)

XDATCAR

read_xdatcar returns a lazy XdatcarFile. Its bounded header exposes the comment, scale, cell, symbols, and integer counts. frames() supports the fixed-cell Direct configuration= layout and repeated-header variable-cell layout, yielding coordinate lexemes without retaining all frames. Compressed paths and context-manager use are supported; an incomplete final block is dropped and reported in issues. The header has cartesian, and every frame mapping has its own cartesian boolean from the configuration marker. Repeated- header frames also expose their validated per-frame scale; fixed-cell frames use None. XdatcarFile.path returns the source filename string.

OSZICAR and POTCAR summary

read_oszicar groups electronic iterations with the following ionic summary. Electronic values and ionic energies remain strings. A trailing electronic block becomes an entry whose ionic fields are None. MD summaries may omit dE; that missing lexeme is retained as None rather than inferred.

read_potcar_summary extracts only header metadata (TITEL, ZVAL, POMASS, ENMAX, and LEXCH) into one mapping per potential. It supports concatenated POTCAR.summary headers and never retains or exposes the full POTCAR text.

VASPOutputs

VASPOutputs(directory) lazily probes POSCAR, CONTCAR, OUTCAR, XDATCAR, OSZICAR, and POTCAR/POTCAR.summary, including every compression suffix registered by httk-core. Missing files return None. Payload-returning properties hold no handles; the composite owns and closes the OutcarFile and XdatcarFile objects it constructs:

from httk.atomistic.integrations.vasp.io import VASPOutputs

with VASPOutputs("calculation") as outputs:
    assert outputs.poscar is not None
    assert outputs.outcar is None or outputs.outcar.version_string.startswith("vasp.")

The runnable Read a small synthetic VASP output directory. walks the composite reader end to end. For WAVECAR files, see Reading and writing VASP WAVECAR files.