Skip to content

Commit

Permalink
VASP Bugfixes: Volumetric data and XDATCAR parsing for monatomic stru…
Browse files Browse the repository at this point in the history
…ctures (#4104)

* fix parsing of monatomic xdatcars / clean up parsing code

* precommit

* make mypy happy

* fix volumetric bug with newer POTCAR hashing

* ruffn

* fix ubuntu-latest test to run on 3.12 as gh workflow indicates, stop trying to run 3.13

* bump down ubuntu-latest to 3.11, mypy too aggressive

* change lint version to 3.11 explicitly

* pin mypy < 1.12 for linting wf
  • Loading branch information
esoteric-ephemera authored Oct 14, 2024
1 parent 668f1aa commit 4f7aa35
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
python-version: '3.11'

- name: Install dependencies
run: |
pip install --upgrade ruff mypy pyright
pip install --upgrade ruff 'mypy<1.12' pyright
- name: ruff
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
resolution: highest
extras: ci,optional,numpy-v1 # Test NP1 on Windows (quite buggy ATM)
- os: ubuntu-latest
python: ">3.10"
python: "3.12"
resolution: lowest-direct
extras: ci,optional
- os: macos-latest
Expand Down
13 changes: 12 additions & 1 deletion src/pymatgen/io/vasp/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,18 @@ def from_str(

except ValueError:
vasp5_symbols = True
symbols: list[str] = [symbol.split("/")[0] for symbol in lines[5].split()]

# In VASP 6.x.x, part of the POTCAR hash is written to POSCAR-style strings
# In VASP 6.4.2 and up, the POTCAR symbol is also written, ex.:
# ```MgSi
# 1.0
# -0.000011 4.138704 0.000002
# -2.981238 2.069353 3.675251
# 2.942054 2.069351 4.865237
# Mg_pv/f474ac0d Si/79d9987ad87```
# whereas older VASP 5.x.x POSCAR strings would just have `Mg Si` on the last line

symbols: list[str] = [symbol.split("/")[0].split("_")[0] for symbol in lines[5].split()]

# Atoms and number of atoms in POSCAR written with VASP appear on
# multiple lines when atoms of the same type are not grouped together
Expand Down
72 changes: 34 additions & 38 deletions src/pymatgen/io/vasp/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4358,68 +4358,64 @@ def __init__(
coords_str: list = []
structures: list = []
preamble_done: bool = False
parse_poscar: bool = False
num_sites: int | None = None
restart_preamble: bool = False
if ionicstep_start < 1:
raise ValueError("Start ionic step cannot be less than 1")
if ionicstep_end is not None and ionicstep_end < 1:
raise ValueError("End ionic step cannot be less than 1")

file_len = sum(1 for _ in zopen(filename, mode="rt"))
ionicstep_cnt = 1
ionicstep_start = ionicstep_start or 0
with zopen(filename, mode="rt") as file:
title = None
for line in file:
for iline, line in enumerate(file):
line = line.strip()
if preamble is None:
preamble = [line]
title = line
elif title == line:

elif title == line and len(coords_str) > 0:
# sometimes the title is the same as the only chemical species in the structure
# only enter this block if the coords have been read
parse_poscar = True
restart_preamble = True
preamble_done = False
poscar = Poscar.from_str("\n".join([*preamble, "Direct", *coords_str]))
if ionicstep_end is None:
if ionicstep_cnt >= ionicstep_start:
structures.append(poscar.structure)
else:
if ionicstep_start <= ionicstep_cnt < ionicstep_end:
structures.append(poscar.structure)
if ionicstep_cnt >= ionicstep_end:
break
ionicstep_cnt += 1
coords_str = []
preamble = [line]

elif not preamble_done:
if line == "" or "Direct configuration=" in line:
preamble_done = True
tmp_preamble = [preamble[0]]
for i in range(1, len(preamble)):
if preamble[0] != preamble[i]:
tmp_preamble.append(preamble[i])
else:
break
preamble = tmp_preamble
else:
preamble.append(line)
elif line == "" or "Direct configuration=" in line:

elif line == "" or "Direct configuration=" in line and len(coords_str) > 0:
parse_poscar = True
restart_preamble = False
else:
coords_str.append(line)

if (parse_poscar and (num_sites is None or len(coords_str) == num_sites)) or iline == file_len - 1:
if num_sites is None:
num_sites = len(coords_str)

poscar = Poscar.from_str("\n".join([*preamble, "Direct", *coords_str]))
if ionicstep_end is None:
if ionicstep_cnt >= ionicstep_start:
structures.append(poscar.structure)
else:
if ionicstep_start <= ionicstep_cnt < ionicstep_end:
structures.append(poscar.structure)
if ionicstep_cnt >= ionicstep_end:
break
if (ionicstep_end is None and ionicstep_cnt >= ionicstep_start) or (
ionicstep_end is not None and ionicstep_start <= ionicstep_cnt < ionicstep_end
):
structures.append(poscar.structure)
elif (ionicstep_end is not None) and ionicstep_cnt >= ionicstep_end:
break

ionicstep_cnt += 1
coords_str = []
else:
coords_str.append(line)
parse_poscar = False
if restart_preamble:
preamble = [line]

if preamble is None:
raise ValueError("preamble is None")
poscar = Poscar.from_str("\n".join([*preamble, "Direct", *coords_str]))
if (
(ionicstep_end is None and ionicstep_cnt >= ionicstep_start)
or ionicstep_start <= ionicstep_cnt < ionicstep_end # type: ignore[operator]
):
structures.append(poscar.structure)

self.structures = structures
self.comment = comment or self.structures[0].formula
Expand Down
Binary file added tests/files/io/vasp/outputs/LOCPOT.vasp642.gz
Binary file not shown.
Binary file added tests/files/io/vasp/outputs/XDATCAR_monatomic.gz
Binary file not shown.
10 changes: 10 additions & 0 deletions tests/io/vasp/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,12 @@ def test_init(self):
l2 = Locpot(poscar=poscar, data=data, data_aug=None)
assert l2.data_aug == {}

def test_vasp_6x_style(self):
filepath = f"{VASP_OUT_DIR}/LOCPOT.vasp642.gz"
locpot = Locpot.from_file(filepath)
assert locpot.dim == (2, 2, 5)
assert {str(ele) for ele in locpot.structure.composition} == {"Mg", "Si"}


class TestChgcar(PymatgenTest):
@classmethod
Expand Down Expand Up @@ -1718,6 +1724,10 @@ def test_init(self):

assert structures[0].lattice != structures[-1].lattice

xdatcar = Xdatcar(f"{VASP_OUT_DIR}/XDATCAR_monatomic.gz")
assert len(xdatcar.structures) == 10
assert all(len(structure.composition) == 1 for structure in xdatcar.structures)


class TestDynmat:
def test_init(self):
Expand Down

0 comments on commit 4f7aa35

Please sign in to comment.