Skip to content

Commit

Permalink
Add capability for Vasprun to read KPOINTS_OPT data (#3509)
Browse files Browse the repository at this point in the history
* Add KPOINTS_OPT reading functionality to Vasprun

Allow reading in KPOINTS_OPT data from vasprun.xml. Also get band structure from KPOINTS_OPT data, if requested. (May change that to the default behaviour.)

I've tested it separately, but still need to test integration into the pymatgen code, once I get a working development build of pymatgen.

* Update docstrings, fix names in Vasprun

projected_magnetism_kpoints_opt -> projected_magnetisation_kpoints_opt

* Test case for Vasprun kpoints_opt

From issue #3455 , dataset supplied by Jeff-oakly #3455 (comment)
Although, for some reason, even though LORBIT = 11, vasprun.xml doesn't have projected eigenvalues for kpoints_opt. Wierd.

* Bandstructure test for kpoints_opt vasprun.xml

* Ran my own calculation, so have projected data

It was with VASP 6.4 rather than 6.3, if that makes a difference. And more CPUs so it snuck in more bands, which slightly changed some energy.

* Unittest for projected kpoints_opt

* Change use_kpoints_opt to be default behaviour

if KPOINTS_OPT data is present.

* Update borg test to account for new test documents

* Rename new test file directory to kpoints_opt

Prior name was not descriptive of the tests being done.

* pre-commit auto-fixes

* Code linting from ruff (whitespace, line length)

* Support for KPOINTS_OPT in BSVasprun

* as_dict now reads KPOINTS_OPT parameters

in both Vasprun and BSVasprun

* tweak Vasprun Attributes: doc str

improve test err message on bad vbm/cbm kpoint labels

* Replace vasprun.xml for kpoints_opt with smaller file

* refactor get_band_structure

tweak var names

* Explanatory comment on parser restructure

#3509

* Stick all KPOINTS_OPT properties in a container

Now they're referenced as attributes of that container (kpoints_opt_properties) rather than being top-level quantities in Vasprun.

* rename _ElectronicPropertiesContainer to KpointOptProps and turn into dataclass

which have 1st class MSONable support

* rename Vasprun.kpoints_opt_properties to kpoint_opt_props

* fix typo kpoint_opt_props -> kpoints_opt_props

* fix mypy

---------

Signed-off-by: Bernard Field <51108483+bfield1@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Janosh Riebesell <janosh.riebesell@gmail.com>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent f6b8236 commit 53141c1
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 176 deletions.
18 changes: 9 additions & 9 deletions dev_scripts/regen_libxcfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,28 @@ def parse_section(section):
return dct


def write_libxc_docs_json(xcfuncs, jpath):
def write_libxc_docs_json(xc_funcs, json_path):
"""Write json file with libxc metadata to path jpath."""
xcfuncs = deepcopy(xcfuncs)
xc_funcs = deepcopy(xc_funcs)

# Remove XC_FAMILY from Family and XC_ from Kind to make strings more human-readable.
for d in xcfuncs.values():
for d in xc_funcs.values():
d["Family"] = d["Family"].replace("XC_FAMILY_", "", 1)
d["Kind"] = d["Kind"].replace("XC_", "", 1)

# Build lightweight version with a subset of keys.
for num, d in xcfuncs.items():
xcfuncs[num] = {key: d[key] for key in ("Family", "Kind", "References")}
for num, d in xc_funcs.items():
xc_funcs[num] = {key: d[key] for key in ("Family", "Kind", "References")}
# Descriptions are optional
for opt in ("Description 1", "Description 2"):
desc = d.get(opt)
if desc is not None:
xcfuncs[num][opt] = desc
xc_funcs[num][opt] = desc

with open(jpath, mode="w") as file:
json.dump(xcfuncs, file)
with open(json_path, "w") as fh:
json.dump(xc_funcs, fh)

return xcfuncs
return xc_funcs


def main():
Expand Down
502 changes: 340 additions & 162 deletions pymatgen/io/vasp/outputs.py

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions tests/apps/borg/test_queen.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@

__author__ = "Shyue Ping Ong"
__copyright__ = "Copyright 2012, The Materials Project"
__version__ = "0.1"
__maintainer__ = "Shyue Ping Ong"
__email__ = "shyue@mit.edu"
__date__ = "Mar 18, 2012"


Expand All @@ -22,7 +19,7 @@ def test_get_data(self):
drone = VaspToComputedEntryDrone()
self.queen = BorgQueen(drone, TEST_FILES_DIR, 1)
data = self.queen.get_data()
assert len(data) == 15
assert len(data) == 16

def test_load_data(self):
drone = VaspToComputedEntryDrone()
Expand Down
4 changes: 4 additions & 0 deletions tests/files/kpoints_opt/KPOINTS
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fully automatic kpoint scheme
0
Automatic
25
33 changes: 33 additions & 0 deletions tests/files/kpoints_opt/KPOINTS_OPT
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Line_mode KPOINTS file
10
Line_mode
Reciprocal
0.0 0.0 0.0 ! \Gamma
0.5 0.0 0.5 ! X

0.5 0.0 0.5 ! X
0.5 0.25 0.75 ! W

0.5 0.25 0.75 ! W
0.375 0.375 0.75 ! K

0.375 0.375 0.75 ! K
0.0 0.0 0.0 ! \Gamma

0.0 0.0 0.0 ! \Gamma
0.5 0.5 0.5 ! L

0.5 0.5 0.5 ! L
0.625 0.25 0.625 ! U

0.625 0.25 0.625 ! U
0.5 0.25 0.75 ! W

0.5 0.25 0.75 ! W
0.5 0.5 0.5 ! L

0.5 0.5 0.5 ! L
0.375 0.375 0.75 ! K

0.625 0.25 0.625 ! U
0.5 0.0 0.5 ! X
Binary file added tests/files/kpoints_opt/vasprun.xml.gz
Binary file not shown.
89 changes: 88 additions & 1 deletion tests/io/vasp/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from pymatgen.core import Element
from pymatgen.core.lattice import Lattice
from pymatgen.core.structure import Structure
from pymatgen.electronic_structure.bandstructure import BandStructureSymmLine
from pymatgen.electronic_structure.core import Magmom, Orbital, OrbitalType, Spin
from pymatgen.entries.compatibility import MaterialsProjectCompatibility
from pymatgen.io.vasp.inputs import Kpoints, Poscar, Potcar
Expand Down Expand Up @@ -47,6 +48,8 @@
except ImportError:
h5py = None

kpts_opt_vrun_path = f"{TEST_FILES_DIR}/kpoints_opt/vasprun.xml.gz"


class TestVasprun(PymatgenTest):
def test_vasprun_ml(self):
Expand All @@ -72,7 +75,9 @@ def test_vasprun_md(self):
assert vasp_run.converged_ionic

def test_bad_random_seed(self):
_ = Vasprun(f"{TEST_FILES_DIR}/vasprun.bad_random_seed.xml.gz")
vasp_run = Vasprun(f"{TEST_FILES_DIR}/vasprun.bad_random_seed.xml.gz")
assert vasp_run.incar["ISMEAR"] == 0
assert vasp_run.incar["RANDOM_SEED"] is None

def test_multiple_dielectric(self):
vasp_run = Vasprun(f"{TEST_FILES_DIR}/vasprun.GW0.xml.gz")
Expand Down Expand Up @@ -704,6 +709,59 @@ def test_eigenvalue_band_properties_separate_spins(self):
assert props[3][0]
assert props[3][1]

def test_kpoints_opt(self):
vasp_run = Vasprun(kpts_opt_vrun_path, parse_projected_eigen=True)
# This calculation was run using KPOINTS_OPT
kpt_opt_props = vasp_run.kpoints_opt_props
# Check the k-points were read correctly.
assert len(vasp_run.actual_kpoints) == 10
assert len(vasp_run.actual_kpoints_weights) == 10
assert len(kpt_opt_props.actual_kpoints) == 100
assert len(kpt_opt_props.actual_kpoints_weights) == 100
# Check the eigenvalues were read correctly.
assert vasp_run.eigenvalues[Spin.up].shape == (10, 24, 2)
assert kpt_opt_props.eigenvalues[Spin.up].shape == (100, 24, 2)
assert vasp_run.eigenvalues[Spin.up][0, 0, 0] == approx(-6.1471)
assert kpt_opt_props.eigenvalues[Spin.up][0, 0, 0] == approx(-6.1536)
# Check the projected eigenvalues were read correctly
assert vasp_run.projected_eigenvalues[Spin.up].shape == (10, 24, 8, 9)
assert kpt_opt_props.projected_eigenvalues[Spin.up].shape == (100, 24, 8, 9)
assert vasp_run.projected_eigenvalues[Spin.up][0, 1, 0, 0] == approx(0.0492)
# I think these zeroes are a bug in VASP (maybe my VASP) transcribing from PROCAR_OPT to vasprun.xml
# No matter. The point of the parser is to read what's in the file.
assert kpt_opt_props.projected_eigenvalues[Spin.up][0, 1, 0, 0] == approx(0.0000)
# Test as_dict
vasp_run_dct = vasp_run.as_dict()
assert vasp_run_dct["input"]["nkpoints_opt"] == 100
assert vasp_run_dct["input"]["nkpoints"] == 10
assert vasp_run_dct["output"]["eigenvalues_kpoints_opt"]["1"][0][0][0] == approx(-6.1536)

def test_kpoints_opt_band_structure(self):
vasp_run = Vasprun(kpts_opt_vrun_path, parse_potcar_file=False, parse_projected_eigen=True)
bs = vasp_run.get_band_structure(f"{TEST_FILES_DIR}/kpoints_opt/KPOINTS_OPT")
assert isinstance(bs, BandStructureSymmLine)
cbm = bs.get_cbm()
vbm = bs.get_vbm()
assert cbm["kpoint_index"] == [38], "wrong cbm kpoint index"
assert cbm["energy"] == approx(6.4394), "wrong cbm energy"
assert cbm["band_index"] == {Spin.up: [16], Spin.down: [16]}, "wrong cbm bands"
assert vbm["kpoint_index"] == [0, 39, 40]
assert vbm["energy"] == approx(5.7562), "wrong vbm energy"
assert vbm["band_index"] == {Spin.down: [13, 14, 15], Spin.up: [13, 14, 15]}, "wrong vbm bands"
vbm_kp_label = vbm["kpoint"].label
assert vbm["kpoint"].label == "\\Gamma", f"Unpexpected {vbm_kp_label=}"
cmb_kp_label = cbm["kpoint"].label
assert cmb_kp_label is None, f"Unpexpected {cmb_kp_label=}"
# Test projection
projected = bs.get_projection_on_elements()
assert np.isnan(projected[Spin.up][0][0]["Si"])
# Due to some error in my VASP, the transcription of PROCAR_OPT into
# vasprun.xml is filled to the brim with errors in the projections.
# At some point we might get a healthier vasprun.xml, but the point here
# is to test the parser, not VASP.
projected = bs.get_projections_on_elements_and_orbitals({"Si": ["s"]})
assert projected[Spin.up][0][58]["Si"]["s"] == -0.0271

def test_parse_potcar_cwd_relative(self):
# Test to ensure that common use cases of vasprun parsing work
# in the current working directory using relative paths,
Expand Down Expand Up @@ -1322,6 +1380,35 @@ def test_get_band_structure(self):
dct = vasprun.as_dict()
assert "eigenvalues" in dct["output"]

def test_kpoints_opt(self):
vasp_run = BSVasprun(kpts_opt_vrun_path, parse_potcar_file=False, parse_projected_eigen=True)
bs = vasp_run.get_band_structure(f"{TEST_FILES_DIR}/kpoints_opt/KPOINTS_OPT")
assert isinstance(bs, BandStructureSymmLine)
cbm = bs.get_cbm()
vbm = bs.get_vbm()
assert cbm["kpoint_index"] == [38], "wrong cbm kpoint index"
assert cbm["energy"] == approx(6.4394), "wrong cbm energy"
assert cbm["band_index"] == {Spin.up: [16], Spin.down: [16]}, "wrong cbm bands"
# Strangely, when I call with parse_projected_eigen, it gives empty Spin.down,
# but without parse_projected_eigen it does not give it.
# So at one point it called the empty key.
assert vbm["kpoint_index"] == [0, 39, 40]
assert vbm["energy"] == approx(5.7562), "wrong vbm energy"
assert vbm["band_index"] == {Spin.down: [13, 14, 15], Spin.up: [13, 14, 15]}, "wrong vbm bands"
assert vbm["kpoint"].label == "\\Gamma", "wrong vbm label"
assert cbm["kpoint"].label is None, "wrong cbm label"
# Test projection
projected = bs.get_projection_on_elements()
assert np.isnan(projected[Spin.up][0][0]["Si"])
# Due to some error in my VASP, the transcription of PROCAR_OPT into
# vasprun.xml is filled to the brim with errors in the projections.
# At some point we might get a healthier vasprun.xml, but the point here
# is to test the parser, not VASP.
projected = bs.get_projections_on_elements_and_orbitals({"Si": ["s"]})
assert projected[Spin.up][0][58]["Si"]["s"] == -0.0271
vrun_dct = vasp_run.as_dict()
assert {*vrun_dct["output"]} >= {"eigenvalues", "eigenvalues_kpoints_opt"}


class TestOszicar(PymatgenTest):
def test_init(self):
Expand Down

0 comments on commit 53141c1

Please sign in to comment.