Reading VASP POSCAR / CONTCAR files

httk-io ships a string-preserving reader for VASP POSCAR/CONTCAR files, httk.io.read_poscar. It parses the file into a neutral, JSON-able mapping whose numeric fields are kept as the verbatim strings found in the file — no floating-point rounding happens at the I/O layer. Turning that mapping into an exact Structure is the separate job of httk.atomistic.structure_from_poscar (see the httk-atomistic docs).

The neutral mapping

read_poscar accepts a filename (str or os.PathLike), an open text stream, or a plain iterable of lines. Here we parse a small VASP-5 cell given as a list of lines:

from httk.io import read_poscar

poscar_lines = [
    "Si primitive\n",
    "1.0\n",
    "0.0 2.7 2.7\n",
    "2.7 0.0 2.7\n",
    "2.7 2.7 0.0\n",
    "Si\n",
    "2\n",
    "Direct\n",
    "0.00 0.00 0.00\n",
    "0.25 0.25 0.25\n",
]

data = read_poscar(poscar_lines)
assert data["format"] == "vasp-poscar"
assert data["comment"] == "Si primitive"
assert data["scale"] == "1.0" and data["volume"] is None
assert data["symbols"] == ["Si"]
assert data["counts"] == [2]
assert data["cartesian"] is False
# Coordinates are preserved exactly as written:
assert data["coords"] == [["0.00", "0.00", "0.00"], ["0.25", "0.25", "0.25"]]
assert data["selective_dynamics"] is None

The scale and volume fields are mutually exclusive: a negative universal scaling factor on line 2 means its absolute value is the target cell volume, so scale is None and volume carries the (sign-stripped) string. A VASP-4 file (atom counts with no species line) sets symbols to None. When the file declares selective dynamics, the per-atom T/F flags are collected into selective_dynamics as booleans; otherwise that field is None. Trailing per-line species labels and velocity blocks are ignored.

Loader registration and load

Importing httk.core discovers httk.handlers.io, which registers the POSCAR loader under the extensions .poscar / .vasp and the exact basenames POSCAR / CONTCAR. httk.core.load therefore dispatches these files automatically, including compressed ones such as CONTCAR.bz2 (the compression suffix is stripped to recognize the basename, and the file is decompressed transparently on read):

import bz2
import tempfile
from pathlib import Path

import httk.core

assert "contcar" in httk.core.register.known_filenames()
assert ".poscar" in httk.core.register.known_extensions()

contcar_text = "He\n1.0\n1 0 0\n0 1 0\n0 0 1\nHe\n1\nDirect\n0 0 0\n"
with tempfile.TemporaryDirectory() as tmp:
    path = Path(tmp) / "CONTCAR.bz2"
    path.write_bytes(bz2.compress(contcar_text.encode("utf-8")))
    data = httk.core.load(str(path))

assert data["format"] == "vasp-poscar"
assert data["symbols"] == ["He"]