Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parse_vasp_dir if no magmoms #148

Merged
merged 4 commits into from
Apr 1, 2024
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
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
pip install build
python -m build --sdist

- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
path: dist/*.tar.gz

Expand All @@ -57,7 +57,7 @@ jobs:
CIBW_ARCHS_MACOS: universal2

- name: Save artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: wheelhouse

Expand All @@ -70,7 +70,7 @@ jobs:
id-token: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: artifact
path: dist
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ default_install_hook_types: [pre-commit, commit-msg]

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.2
rev: v0.3.4
hooks:
- id: ruff
args: [--fix]
Expand Down Expand Up @@ -46,7 +46,7 @@ repos:
- svelte

- repo: https://github.com/pre-commit/mirrors-eslint
rev: v9.0.0-beta.2
rev: v9.0.0-rc.0
hooks:
- id: eslint
types: [file]
Expand Down
53 changes: 28 additions & 25 deletions chgnet/utils/vasp_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import os.path
import re
from typing import TYPE_CHECKING

Expand All @@ -22,28 +23,29 @@ def parse_vasp_dir(
check_electronic_convergence (bool): if set to True, this function will raise
Exception to VASP calculation that did not achieve electronic convergence.
"""
try:
oszicar = Oszicar(f"{file_root}/OSZICAR")
vasprun_orig = Vasprun(
f"{file_root}/vasprun.xml",
parse_dos=False,
parse_eigen=False,
parse_projected_eigen=False,
parse_potcar_file=False,
exception_on_bad_xml=False,
)
outcar_filename = f"{file_root}/OUTCAR"
except Exception:
oszicar = Oszicar(f"{file_root}/OSZICAR.gz")
vasprun_orig = Vasprun(
f"{file_root}/vasprun.xml.gz",
parse_dos=False,
parse_eigen=False,
parse_projected_eigen=False,
parse_potcar_file=False,
exception_on_bad_xml=False,
)
outcar_filename = f"{file_root}/OUTCAR.gz"
if os.path.exists(file_root) is False:
raise FileNotFoundError("No such file or directory")

if os.path.exists(f"{file_root}/OSZICAR"):
oszicar_path = f"{file_root}/OSZICAR"
vasprun_path = f"{file_root}/vasprun.xml"
outcar_path = f"{file_root}/OUTCAR"
elif os.path.exists(f"{file_root}/OSZICAR"):
oszicar_path = f"{file_root}/OSZICAR.gz"
vasprun_path = f"{file_root}/vasprun.xml.gz"
outcar_path = f"{file_root}/OUTCAR.gz"
else:
raise RuntimeError(f"No data parsed from {file_root}!")

oszicar = Oszicar(oszicar_path)
vasprun_orig = Vasprun(
vasprun_path,
parse_dos=False,
parse_eigen=False,
parse_projected_eigen=False,
parse_potcar_file=False,
exception_on_bad_xml=False,
)

charge = []
mag_x = []
Expand All @@ -52,7 +54,7 @@ def parse_vasp_dir(
header = []
all_lines = []

for line in reverse_readfile(outcar_filename):
for line in reverse_readfile(outcar_path):
clean = line.strip()
all_lines.append(clean)

Expand Down Expand Up @@ -132,7 +134,7 @@ def parse_vasp_dir(
"stress": None if "stress" not in vasprun_orig.ionic_steps[0] else [],
}

for ionic_step, mag_step in zip(vasprun_orig.ionic_steps, mag_x_all):
for index, ionic_step in enumerate(vasprun_orig.ionic_steps):
if (
check_electronic_convergence
and len(ionic_step["electronic_steps"]) >= vasprun_orig.parameters["NELM"]
Expand All @@ -143,7 +145,8 @@ def parse_vasp_dir(
dataset["uncorrected_total_energy"].append(ionic_step["e_0_energy"])
dataset["energy_per_atom"].append(ionic_step["e_0_energy"] / n_atoms)
dataset["force"].append(ionic_step["forces"])
dataset["magmom"].append([site["tot"] for site in mag_step])
if mag_x_all != []:
dataset["magmom"].append([site["tot"] for site in mag_x_all[index]])
if "stress" in ionic_step:
dataset["stress"].append(ionic_step["stress"])

Expand Down
Binary file added tests/files/parse-vasp-no-magmoms.zip
Binary file not shown.
Binary file added tests/files/parse-vasp-with-magmoms.zip
Binary file not shown.
69 changes: 69 additions & 0 deletions tests/test_vasp_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from __future__ import annotations

import os.path
from typing import TYPE_CHECKING
from zipfile import ZipFile

import pytest
from pymatgen.core import Structure

from chgnet import ROOT
from chgnet.utils import parse_vasp_dir

if TYPE_CHECKING:
from pathlib import Path


def test_parse_vasp_dir_with_magmoms(tmp_path: Path):
with ZipFile(f"{ROOT}/tests/files/parse-vasp-with-magmoms.zip") as zip_ref:
zip_ref.extractall(tmp_path)
vasp_path = os.path.join(tmp_path, "parse-vasp-with-magmoms")
dataset_dict = parse_vasp_dir(vasp_path)

assert isinstance(dataset_dict, dict)
assert len(dataset_dict["structure"]) > 0
assert len(dataset_dict["uncorrected_total_energy"]) > 0
assert len(dataset_dict["energy_per_atom"]) > 0
assert len(dataset_dict["force"]) > 0
assert len(dataset_dict["magmom"]) == len(dataset_dict["force"])
assert len(dataset_dict["stress"]) > 0

for structure in dataset_dict["structure"]:
assert isinstance(structure, Structure)

for magmom in dataset_dict["magmom"]:
assert len(magmom) == len(dataset_dict["structure"][0])


def test_parse_vasp_dir_without_magmoms(tmp_path: Path):
# using test.zip shared for error repro in
# https://github.com/CederGroupHub/chgnet/issues/147
with ZipFile(f"{ROOT}/tests/files/parse-vasp-no-magmoms.zip") as zip_ref:
zip_ref.extractall(tmp_path)
vasp_path = os.path.join(tmp_path, "parse-vasp-no-magmoms")
dataset_dict = parse_vasp_dir(vasp_path)

assert isinstance(dataset_dict, dict)
assert len(dataset_dict["structure"]) > 0
assert len(dataset_dict["uncorrected_total_energy"]) > 0
assert len(dataset_dict["energy_per_atom"]) > 0
assert len(dataset_dict["force"]) > 0
assert len(dataset_dict["magmom"]) == 0
assert len(dataset_dict["stress"]) > 0

for structure in dataset_dict["structure"]:
assert isinstance(structure, Structure)

for magmom in dataset_dict["magmom"]:
assert len(magmom) == len(dataset_dict["structure"][0])
assert all(mag == 0.0 for mag in magmom)


def test_parse_vasp_dir_no_data():
# test non-existing directory
with pytest.raises(FileNotFoundError, match="No such file or directory"):
parse_vasp_dir(f"{ROOT}/tests/files/non-existent")

# test existing directory without VASP files
with pytest.raises(RuntimeError, match="No data parsed from"):
parse_vasp_dir(f"{ROOT}/tests/files")
Loading