From 63951e3b681201345836e6a665625b008e55e71a Mon Sep 17 00:00:00 2001 From: certural Date: Mon, 6 Mar 2023 18:07:50 +0100 Subject: [PATCH 01/26] Implemented a pymatgen class to handle NcICOBILIST.lobster files --- pymatgen/io/lobster/__init__.py | 1 + pymatgen/io/lobster/outputs.py | 123 +++++++++++++++++- pymatgen/io/lobster/tests/test_lobster.py | 44 +++++++ test_files/cohp/NcICOBILIST.lobster | 50 +++++++ test_files/cohp/NcICOBILIST.lobster.gz | Bin 0 -> 360 bytes test_files/cohp/NcICOBILIST.lobster.nospin | 25 ++++ ...NcICOBILIST.lobster.nospin.withoutorbitals | 3 + .../cohp/NcICOBILIST.lobster.withoutorbitals | 6 + 8 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 test_files/cohp/NcICOBILIST.lobster create mode 100644 test_files/cohp/NcICOBILIST.lobster.gz create mode 100644 test_files/cohp/NcICOBILIST.lobster.nospin create mode 100644 test_files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals create mode 100644 test_files/cohp/NcICOBILIST.lobster.withoutorbitals diff --git a/pymatgen/io/lobster/__init__.py b/pymatgen/io/lobster/__init__.py index 32545b4ccd0..af076d933ea 100644 --- a/pymatgen/io/lobster/__init__.py +++ b/pymatgen/io/lobster/__init__.py @@ -23,4 +23,5 @@ MadelungEnergies, SitePotential, Wavefunction, + Ncicobilist, ) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index a537e37693e..fcd4daa7f22 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -38,7 +38,7 @@ class Cohpcar: """ - Class to read COHPCAR/COOPCAR files generated by LOBSTER. + Class to read COHPCAR/COOPCAR/COBICAR files generated by LOBSTER. .. attribute: cohp_data @@ -78,7 +78,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s Args: are_coops: Determines if the file is a list of COHPs or COOPs. Default is False for COHPs. - are_cobis: Determines if the file is a list of COHPs or COOPs. + are_cobis: Determines if the file is a list of COHPs or COBIs. Default is False for COHPs. filename: Name of the COHPCAR file. If it is None, the default @@ -258,7 +258,7 @@ class Icohplist: Class to read ICOHPLIST/ICOOPLIST files generated by LOBSTER. .. attribute: are_coops - Boolean to indicate if the populations are COOPs or COHPs. + Boolean to indicate if the populations are COOPs, COHPs or COBIs. .. attribute: is_spin_polarized Boolean to indicate if the calculation is spin polarized. @@ -425,6 +425,123 @@ def icohpcollection(self): """ return self._icohpcollection +class Ncicobilist: + """ + Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. + + .. attribute: is_spin_polarized + Boolean to indicate if the calculation is spin polarized. + + .. attribute: Ncicobilist + Dict containing the listfile data of the form: + {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, + "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, + "interaction_type": type of the multi-center interaction + + """ + + def __init__(self, filename: str | None = None): + """ + Args: + filename: Name of the NcICOBILIST file. + """ + + if filename is None: filename = "NcICOBILIST.lobster" + + # LOBSTER list files have an extra trailing blank line + # and we don't need the header. + with zopen(filename, "rt") as f: + data = f.read().split("\n")[1:-1] + if len(data) == 0: + raise OSError("NcICOBILIST file contains no data.") + + # # Which LOBSTER version? + # if len(data[0].split()) == 8: + # version = "3.1.1" + # elif len(data[0].split()) == 6: + # version = "2.2.1" + # warnings.warn("Please consider using the new LOBSTER version. See www.cohp.de.") + # else: + # raise ValueError + + # If the calculation is spin polarized, the line in the middle + # of the file will be another header line. + if "spin" in data[len(data) // 2]: + # TODO: adapt this for orbitalwise stuff + self.is_spin_polarized = True + else: + self.is_spin_polarized = False + + # check if orbitalwise NcICOBILIST + # include case when there is only one NcICOBI + for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed + if len(data) > 2 and "s]" in str(entry.split()[3:]): + self.orbitalwise = True + warnings.warn( + "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " + + "information is not read!") + break # condition has only to be met once + else: + self.orbitalwise = False + + if self.orbitalwise: + data_without_orbitals = [] + for line in data: + if "_" not in str(line.split()[3:]): + if "s]" not in str(line.split()[3:]): + data_without_orbitals.append(line) + else: + data_without_orbitals = data + + if "spin" in data_without_orbitals[len(data_without_orbitals) // 2]: + # TODO: adapt this for orbitalwise stuff + num_bonds = len(data_without_orbitals) // 2 + if num_bonds == 0: + raise OSError("NcICOBILIST file contains no data.") + else: + num_bonds = len(data_without_orbitals) + + self.list_labels = [] + self.list_numofatoms = [] + self.list_ncicobi = [] + self.list_interactiontype = [] + self.list_num = [] + + for bond in range(num_bonds): + line = data_without_orbitals[bond].split() + ncicobi = {} + + label = f"{line[0]}" + numofatoms = str(line[1]) + ncicobi[Spin.up] = float(line[2]) + interactiontype = str(line[3:]).replace("'", "").replace(" ", "") + num = int(1) + + if self.is_spin_polarized: + ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) + + self.list_labels.append(label) + self.list_numofatoms.append(numofatoms) + self.list_ncicobi.append(ncicobi) + self.list_interactiontype.append(interactiontype) + self.list_num.append(num) + + # TODO: add functions to get orbital resolved NcICOBIs + + @property + def ncicobilist(self) -> dict[Any, dict[str, Any]]: + """ + Returns: ncicobilist. + """ + ncicobilist = {} + for key, entry in enumerate(self.list_labels): + ncicobilist[str(key + 1)] = { + "number_of_atoms": int(self.list_numofatoms[key]), + "ncicobi": self.list_ncicobi[key], + "interaction_type": self.list_interactiontype[key] + } + + return ncicobilist class Doscar: """ diff --git a/pymatgen/io/lobster/tests/test_lobster.py b/pymatgen/io/lobster/tests/test_lobster.py index 32bd4a06523..66ceeb11960 100644 --- a/pymatgen/io/lobster/tests/test_lobster.py +++ b/pymatgen/io/lobster/tests/test_lobster.py @@ -28,6 +28,7 @@ MadelungEnergies, SitePotential, Wavefunction, + Ncicobilist, ) from pymatgen.io.lobster.inputs import get_all_possible_basis_combinations from pymatgen.io.vasp import Vasprun @@ -653,6 +654,49 @@ def test_values(self): assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["icohp"][Spin.down] == approx(0.58649 / 2, abs=1e-3) assert self.icobi.icohpcollection.extremum_icohpvalue() == 0.58649 +class NcicobilistTest(unittest.TestCase): + def setUp(self): + self.ncicobi = Ncicobilist( + filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster") + ) + + self.ncicobigz = Ncicobilist( + filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.gz") + ) + + self.ncicobinospin = Ncicobilist( + filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.nospin") + ) + + self.ncicobinospinwo = Ncicobilist( + filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.nospin.withoutorbitals") + ) + + self.ncicobiwo = Ncicobilist( + filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.withoutorbitals") + ) + def test_ncicobilist(self): + self.assertTrue(self.ncicobi.is_spin_polarized) + self.assertFalse(self.ncicobinospin.is_spin_polarized) + self.assertTrue(self.ncicobiwo.is_spin_polarized) + self.assertFalse(self.ncicobinospinwo.is_spin_polarized) + self.assertTrue(self.ncicobi.orbitalwise) + self.assertTrue(self.ncicobinospin.orbitalwise) + self.assertFalse(self.ncicobiwo.orbitalwise) + self.assertFalse(self.ncicobinospinwo.orbitalwise) + self.assertEqual(len(self.ncicobi.ncicobilist), 2) + self.assertEqual(self.ncicobi.ncicobilist["2"]["number_of_atoms"], 3) + self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], 0.00009, places=5) + self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down], 0.00009, places=5) + self.assertEqual(self.ncicobi.ncicobilist["2"]["interaction_type"], "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]") + self.assertEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], + self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up]) + self.assertEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], + self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up]) + self.assertEqual(self.ncicobi.ncicobilist["2"]["interaction_type"], + self.ncicobigz.ncicobilist["2"]["interaction_type"]) + self.assertAlmostEqual(sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())), + self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up], places=5) class DoscarTest(unittest.TestCase): def setUp(self): diff --git a/test_files/cohp/NcICOBILIST.lobster b/test_files/cohp/NcICOBILIST.lobster new file mode 100644 index 00000000000..8538d9ae627 --- /dev/null +++ b/test_files/cohp/NcICOBILIST.lobster @@ -0,0 +1,50 @@ + COBI# No. of atoms Nc-ICOBI (at) eF for spin 1 Atoms for Nc-ICOBI + 1 2 0.00000 X1[0 0 0]->X20 0 0] + 2 3 0.00009 X22[0 0 0]->Xs42[0 0 0]->X31[0 0 0] + 2 3 0.00116 X22[5s]->Xs42[3s]->X31[5s] + 2 3 0.00001 X22[5s]->Xs42[3s]->X31[4d_xy] + 2 3 -0.00003 X22[5s]->Xs42[3s]->X31[4d_yz] + 2 3 -0.00000 X22[5s]->Xs42[3s]->X31[4d_z^2] + 2 3 0.00000 X22[5s]->Xs42[3s]->X31[4d_xz] + 2 3 0.00001 X22[5s]->Xs42[3s]->X31[4d_x^2-y^2] + 2 3 0.00117 X22[5s]->Xs42[3p_y]->X31[5s] + 2 3 -0.00002 X22[5s]->Xs42[3p_y]->X31[4d_xy] + 2 3 -0.00003 X22[5s]->Xs42[3p_y]->X31[4d_yz] + 2 3 0.00001 X22[5s]->Xs42[3p_y]->X31[4d_z^2] + 2 3 -0.00000 X22[5s]->Xs42[3p_y]->X31[4d_xz] + 2 3 0.00003 X22[5s]->Xs42[3p_y]->X31[4d_x^2-y^2] + 2 3 -0.00071 X22[5s]->Xs42[3p_z]->X31[5s] + 2 3 -0.00000 X22[5s]->Xs42[3p_z]->X31[4d_xy] + 2 3 0.00004 X22[5s]->Xs42[3p_z]->X31[4d_yz] + 2 3 -0.00002 X22[5s]->Xs42[3p_z]->X31[4d_z^2] + 2 3 0.00000 X22[5s]->Xs42[3p_z]->X31[4d_xz] + 2 3 -0.00002 X22[5s]->Xs42[3p_z]->X31[4d_x^2-y^2] + 2 3 -0.00149 X22[5s]->Xs42[3p_x]->X31[5s] + 2 3 0.00000 X22[4d_x^2-y^2]->Xs42[3p]->X31[4d_x^2-y^2] + 2 3 -0.00007 X22[4d_x^2-y^2]->Xs42[3p_y]->X31[5s] + 2 3 0.00016 X22[4d_x^2-y^2]->Xs42[3p_x]->X31[5s] + COBI# No. of atoms Nc-ICOBI (at) eF for spin 2 Atoms for Nc-ICOBI + 1 2 0.00000 X1[0 0 0]->X2[0 0 0] + 2 3 0.00009 X22[0 0 0]->Xs42[0 0 0]->X31[0 0 0] + 2 3 0.00116 X22[5s]->Xs42[3s]->X31[5s] + 2 3 0.00001 X22[5s]->Xs42[3s]->X31[4d_xy] + 2 3 -0.00003 X22[5s]->Xs42[3s]->X31[4d_yz] + 2 3 -0.00000 X22[5s]->Xs42[3s]->X31[4d_z^2] + 2 3 0.00000 X22[5s]->Xs42[3s]->X31[4d_xz] + 2 3 0.00001 X22[5s]->Xs42[3s]->X31[4d_x^2-y^2] + 2 3 0.00117 X22[5s]->Xs42[3p_y]->X31[5s] + 2 3 -0.00002 X22[5s]->Xs42[3p_y]->X31[4d_xy] + 2 3 -0.00003 X22[5s]->Xs42[3p_y]->X31[4d_yz] + 2 3 0.00001 X22[5s]->Xs42[3p_y]->X31[4d_z^2] + 2 3 -0.00000 X22[5s]->Xs42[3p_y]->X31[4d_xz] + 2 3 0.00003 X22[5s]->Xs42[3p_y]->X31[4d_x^2-y^2] + 2 3 -0.00071 X22[5s]->Xs42[3p_z]->X31[5s] + 2 3 -0.00000 X22[5s]->Xs42[3p_z]->X31[4d_xy] + 2 3 0.00004 X22[5s]->Xs42[3p_z]->X31[4d_yz] + 2 3 -0.00002 X22[5s]->Xs42[3p_z]->X31[4d_z^2] + 2 3 0.00000 X22[5s]->Xs42[3p_z]->X31[4d_xz] + 2 3 -0.00002 X22[5s]->Xs42[3p_z]->X31[4d_x^2-y^2] + 2 3 -0.00149 X22[5s]->Xs42[3p_x]->X31[5s] + 2 3 0.00000 X22[4d_x^2-y^2]->Xs42[3p]->X31[4d_x^2-y^2] + 2 3 -0.00007 X22[4d_x^2-y^2]->Xs42[3p_y]->X31[5s] + 2 3 0.00016 X22[4d_x^2-y^2]->Xs42[3p_x]->X31[5s] diff --git a/test_files/cohp/NcICOBILIST.lobster.gz b/test_files/cohp/NcICOBILIST.lobster.gz new file mode 100644 index 0000000000000000000000000000000000000000..f8f7e4818c6d47cd34efbabba09972ee4a6c625d GIT binary patch literal 360 zcmV-u0hj(CiwFoq(*a}v15RT}Lr+3UOi5EzE^KdNb97~L0PU8&PQx%1h4((ikyv0z zz+j0Qbch z#Tu(!u~GHtIhi$2aK74I;Pnpc;sa{?_6`EB_iLJ&ZrKQXBUmQkG%*&90dsm3sIRAF zI+x~duL>j+&|#Sx+$>rCwh4vMj40WMYu14_Rpr zQuvU1AvuwInPr%pe@f;;CKbn3qWlMUyR3L+Zxbp##1j_Yf(DkpH>VafwD`RZWyjBQ z^_TcL4Pe~OT}jYKHGnGtn%w|x0y008xA$GGst4HpcKk!Q%5DJ4CJu6W)Q`VSz=2y| z=*8I^q-e{PgRO G5C8yb-=ft3 literal 0 HcmV?d00001 diff --git a/test_files/cohp/NcICOBILIST.lobster.nospin b/test_files/cohp/NcICOBILIST.lobster.nospin new file mode 100644 index 00000000000..9065c349ff1 --- /dev/null +++ b/test_files/cohp/NcICOBILIST.lobster.nospin @@ -0,0 +1,25 @@ + COBI# No. of atoms Nc-ICOBI (at) eF for spin 1 Atoms for Nc-ICOBI + 1 2 0.00000 X1[0 0 0]->X20 0 0] + 2 3 0.00018 X22[0 0 0]->Xs42[0 0 0]->X31[0 0 0] + 2 3 0.00232 X22[5s]->Xs42[3s]->X31[5s] + 2 3 0.00002 X22[5s]->Xs42[3s]->X31[4d_xy] + 2 3 -0.00006 X22[5s]->Xs42[3s]->X31[4d_yz] + 2 3 -0.00000 X22[5s]->Xs42[3s]->X31[4d_z^2] + 2 3 0.00000 X22[5s]->Xs42[3s]->X31[4d_xz] + 2 3 0.00002 X22[5s]->Xs42[3s]->X31[4d_x^2-y^2] + 2 3 0.00234 X22[5s]->Xs42[3p_y]->X31[5s] + 2 3 -0.00004 X22[5s]->Xs42[3p_y]->X31[4d_xy] + 2 3 -0.00006 X22[5s]->Xs42[3p_y]->X31[4d_yz] + 2 3 0.00002 X22[5s]->Xs42[3p_y]->X31[4d_z^2] + 2 3 -0.00000 X22[5s]->Xs42[3p_y]->X31[4d_xz] + 2 3 0.00006 X22[5s]->Xs42[3p_y]->X31[4d_x^2-y^2] + 2 3 -0.00142 X22[5s]->Xs42[3p_z]->X31[5s] + 2 3 -0.00000 X22[5s]->Xs42[3p_z]->X31[4d_xy] + 2 3 0.00008 X22[5s]->Xs42[3p_z]->X31[4d_yz] + 2 3 -0.00004 X22[5s]->Xs42[3p_z]->X31[4d_z^2] + 2 3 0.00000 X22[5s]->Xs42[3p_z]->X31[4d_xz] + 2 3 -0.00004 X22[5s]->Xs42[3p_z]->X31[4d_x^2-y^2] + 2 3 -0.00298 X22[5s]->Xs42[3p_x]->X31[5s] + 2 3 0.00000 X22[4d_x^2-y^2]->Xs42[3p]->X31[4d_x^2-y^2] + 2 3 -0.00014 X22[4d_x^2-y^2]->Xs42[3p_y]->X31[5s] + 2 3 0.00032 X22[4d_x^2-y^2]->Xs42[3p_x]->X31[5s] diff --git a/test_files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals b/test_files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals new file mode 100644 index 00000000000..6ec86056982 --- /dev/null +++ b/test_files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals @@ -0,0 +1,3 @@ + COBI# No. of atoms Nc-ICOBI (at) eF for spin 1 Atoms for Nc-ICOBI + 1 2 0.00000 X1[0 0 0]->X20 0 0] + 2 3 0.00018 X22[0 0 0]->Xs42[0 0 0]->X31[0 0 0] diff --git a/test_files/cohp/NcICOBILIST.lobster.withoutorbitals b/test_files/cohp/NcICOBILIST.lobster.withoutorbitals new file mode 100644 index 00000000000..007bab3c795 --- /dev/null +++ b/test_files/cohp/NcICOBILIST.lobster.withoutorbitals @@ -0,0 +1,6 @@ + COBI# No. of atoms Nc-ICOBI (at) eF for spin 1 Atoms for Nc-ICOBI + 1 2 0.00000 X1[0 0 0]->X20 0 0] + 2 3 0.00009 X22[0 0 0]->Xs42[0 0 0]->X31[0 0 0] + COBI# No. of atoms Nc-ICOBI (at) eF for spin 2 Atoms for Nc-ICOBI + 1 2 0.00000 X1[0 0 0]->X2[0 0 0] + 2 3 0.00009 X22[0 0 0]->Xs42[0 0 0]->X31[0 0 0] From 44b802d364320736cbb6e96ee67273ac3b823b65 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 17:27:42 +0000 Subject: [PATCH 02/26] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pymatgen/io/lobster/__init__.py | 2 +- pymatgen/io/lobster/outputs.py | 18 ++++++++----- pymatgen/io/lobster/tests/test_lobster.py | 31 ++++++++++++++--------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/pymatgen/io/lobster/__init__.py b/pymatgen/io/lobster/__init__.py index af076d933ea..c4c4a1cbcff 100644 --- a/pymatgen/io/lobster/__init__.py +++ b/pymatgen/io/lobster/__init__.py @@ -21,7 +21,7 @@ Icohplist, Lobsterout, MadelungEnergies, + Ncicobilist, SitePotential, Wavefunction, - Ncicobilist, ) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index fcd4daa7f22..c5c44910620 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -425,6 +425,7 @@ def icohpcollection(self): """ return self._icohpcollection + class Ncicobilist: """ Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. @@ -446,7 +447,8 @@ def __init__(self, filename: str | None = None): filename: Name of the NcICOBILIST file. """ - if filename is None: filename = "NcICOBILIST.lobster" + if filename is None: + filename = "NcICOBILIST.lobster" # LOBSTER list files have an extra trailing blank line # and we don't need the header. @@ -474,13 +476,14 @@ def __init__(self, filename: str | None = None): # check if orbitalwise NcICOBILIST # include case when there is only one NcICOBI - for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed + for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed if len(data) > 2 and "s]" in str(entry.split()[3:]): self.orbitalwise = True warnings.warn( - "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " + - "information is not read!") - break # condition has only to be met once + "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " + + "information is not read!" + ) + break # condition has only to be met once else: self.orbitalwise = False @@ -534,15 +537,16 @@ def ncicobilist(self) -> dict[Any, dict[str, Any]]: Returns: ncicobilist. """ ncicobilist = {} - for key, entry in enumerate(self.list_labels): + for key, _entry in enumerate(self.list_labels): ncicobilist[str(key + 1)] = { "number_of_atoms": int(self.list_numofatoms[key]), "ncicobi": self.list_ncicobi[key], - "interaction_type": self.list_interactiontype[key] + "interaction_type": self.list_interactiontype[key], } return ncicobilist + class Doscar: """ Class to deal with Lobster's projected DOS and local projected DOS. diff --git a/pymatgen/io/lobster/tests/test_lobster.py b/pymatgen/io/lobster/tests/test_lobster.py index 66ceeb11960..3fe023fdcbd 100644 --- a/pymatgen/io/lobster/tests/test_lobster.py +++ b/pymatgen/io/lobster/tests/test_lobster.py @@ -26,9 +26,9 @@ Lobsterin, Lobsterout, MadelungEnergies, + Ncicobilist, SitePotential, Wavefunction, - Ncicobilist, ) from pymatgen.io.lobster.inputs import get_all_possible_basis_combinations from pymatgen.io.vasp import Vasprun @@ -654,11 +654,10 @@ def test_values(self): assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["icohp"][Spin.down] == approx(0.58649 / 2, abs=1e-3) assert self.icobi.icohpcollection.extremum_icohpvalue() == 0.58649 + class NcicobilistTest(unittest.TestCase): def setUp(self): - self.ncicobi = Ncicobilist( - filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster") - ) + self.ncicobi = Ncicobilist(filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster")) self.ncicobigz = Ncicobilist( filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.gz") @@ -675,6 +674,7 @@ def setUp(self): self.ncicobiwo = Ncicobilist( filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.withoutorbitals") ) + def test_ncicobilist(self): self.assertTrue(self.ncicobi.is_spin_polarized) self.assertFalse(self.ncicobinospin.is_spin_polarized) @@ -689,14 +689,21 @@ def test_ncicobilist(self): self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], 0.00009, places=5) self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down], 0.00009, places=5) self.assertEqual(self.ncicobi.ncicobilist["2"]["interaction_type"], "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]") - self.assertEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], - self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up]) - self.assertEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], - self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up]) - self.assertEqual(self.ncicobi.ncicobilist["2"]["interaction_type"], - self.ncicobigz.ncicobilist["2"]["interaction_type"]) - self.assertAlmostEqual(sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())), - self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up], places=5) + self.assertEqual( + self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] + ) + self.assertEqual( + self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] + ) + self.assertEqual( + self.ncicobi.ncicobilist["2"]["interaction_type"], self.ncicobigz.ncicobilist["2"]["interaction_type"] + ) + self.assertAlmostEqual( + sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())), + self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up], + places=5, + ) + class DoscarTest(unittest.TestCase): def setUp(self): From 2b9832959903c1d7abd305fa89dcf6ebeed3f8c1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Mar 2023 17:45:57 +0000 Subject: [PATCH 03/26] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pymatgen/core/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/core/__init__.py b/pymatgen/core/__init__.py index 6ccf5f4ece9..c6ec12d9635 100644 --- a/pymatgen/core/__init__.py +++ b/pymatgen/core/__init__.py @@ -46,7 +46,7 @@ def _load_pmg_settings() -> dict[str, Any]: yaml = YAML() for file_path in (SETTINGS_FILE, OLD_SETTINGS_FILE): try: - with open(file_path, "rt", encoding="utf-8") as yml_file: + with open(file_path, encoding="utf-8") as yml_file: settings = yaml.load(yml_file) or {} break except FileNotFoundError: From a3f476d7d7f98d05b531af08b5111c86255883e0 Mon Sep 17 00:00:00 2001 From: certural Date: Mon, 13 Mar 2023 19:05:08 +0100 Subject: [PATCH 04/26] checking lobster version --- pymatgen/io/lobster/outputs.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index c5c44910620..8a1bafbd8e4 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -448,6 +448,7 @@ def __init__(self, filename: str | None = None): """ if filename is None: + warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") filename = "NcICOBILIST.lobster" # LOBSTER list files have an extra trailing blank line @@ -457,15 +458,6 @@ def __init__(self, filename: str | None = None): if len(data) == 0: raise OSError("NcICOBILIST file contains no data.") - # # Which LOBSTER version? - # if len(data[0].split()) == 8: - # version = "3.1.1" - # elif len(data[0].split()) == 6: - # version = "2.2.1" - # warnings.warn("Please consider using the new LOBSTER version. See www.cohp.de.") - # else: - # raise ValueError - # If the calculation is spin polarized, the line in the middle # of the file will be another header line. if "spin" in data[len(data) // 2]: From fdb2be22a9f2d5b20ee4bd0760ad182a3693087d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 16:34:11 +0000 Subject: [PATCH 05/26] pre-commit auto-fixes --- pymatgen/io/lobster/outputs.py | 5 ++-- pymatgen/io/lobster/tests/test_lobster.py | 34 ++++++++++------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index 0a2585cb2d7..2ae04213f28 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -487,9 +487,8 @@ def __init__(self, filename: str | None = None): if self.orbitalwise: data_without_orbitals = [] for line in data: - if "_" not in str(line.split()[3:]): - if "s]" not in str(line.split()[3:]): - data_without_orbitals.append(line) + if "_" not in str(line.split()[3:]) and "s]" not in str(line.split()[3:]): + data_without_orbitals.append(line) else: data_without_orbitals = data diff --git a/pymatgen/io/lobster/tests/test_lobster.py b/pymatgen/io/lobster/tests/test_lobster.py index e8ca528d0e1..b4de4930813 100644 --- a/pymatgen/io/lobster/tests/test_lobster.py +++ b/pymatgen/io/lobster/tests/test_lobster.py @@ -700,28 +700,22 @@ def setUp(self): ) def test_ncicobilist(self): - self.assertTrue(self.ncicobi.is_spin_polarized) - self.assertFalse(self.ncicobinospin.is_spin_polarized) - self.assertTrue(self.ncicobiwo.is_spin_polarized) - self.assertFalse(self.ncicobinospinwo.is_spin_polarized) - self.assertTrue(self.ncicobi.orbitalwise) - self.assertTrue(self.ncicobinospin.orbitalwise) - self.assertFalse(self.ncicobiwo.orbitalwise) - self.assertFalse(self.ncicobinospinwo.orbitalwise) - self.assertEqual(len(self.ncicobi.ncicobilist), 2) - self.assertEqual(self.ncicobi.ncicobilist["2"]["number_of_atoms"], 3) + assert self.ncicobi.is_spin_polarized + assert not self.ncicobinospin.is_spin_polarized + assert self.ncicobiwo.is_spin_polarized + assert not self.ncicobinospinwo.is_spin_polarized + assert self.ncicobi.orbitalwise + assert self.ncicobinospin.orbitalwise + assert not self.ncicobiwo.orbitalwise + assert not self.ncicobinospinwo.orbitalwise + assert len(self.ncicobi.ncicobilist) == 2 + assert self.ncicobi.ncicobilist["2"]["number_of_atoms"] == 3 self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], 0.00009, places=5) self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down], 0.00009, places=5) - self.assertEqual(self.ncicobi.ncicobilist["2"]["interaction_type"], "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]") - self.assertEqual( - self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] - ) - self.assertEqual( - self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] - ) - self.assertEqual( - self.ncicobi.ncicobilist["2"]["interaction_type"], self.ncicobigz.ncicobilist["2"]["interaction_type"] - ) + assert self.ncicobi.ncicobilist["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" + assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobigz.ncicobilist["2"]["interaction_type"] self.assertAlmostEqual( sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())), self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up], From da8a14f611271f9f8a0c8ca131f07b1e2ca3870a Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Fri, 22 Sep 2023 09:20:41 +0200 Subject: [PATCH 06/26] improved the tests for ncicobi --- pymatgen/io/lobster/tests/test_lobster.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pymatgen/io/lobster/tests/test_lobster.py b/pymatgen/io/lobster/tests/test_lobster.py index 16e8125c2a2..e1bcb44021e 100644 --- a/pymatgen/io/lobster/tests/test_lobster.py +++ b/pymatgen/io/lobster/tests/test_lobster.py @@ -711,17 +711,13 @@ def test_ncicobilist(self): assert not self.ncicobinospinwo.orbitalwise assert len(self.ncicobi.ncicobilist) == 2 assert self.ncicobi.ncicobilist["2"]["number_of_atoms"] == 3 - self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up], 0.00009, places=5) - self.assertAlmostEqual(self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down], 0.00009, places=5) + assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == approx(0.00009) + assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down] == approx(0.00009) assert self.ncicobi.ncicobilist["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobigz.ncicobilist["2"]["interaction_type"] - self.assertAlmostEqual( - sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())), - self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up], - places=5, - ) + assert sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())) ==approx(self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up]) class DoscarTest(unittest.TestCase): From 9a94cf1262603b4d7ffb63182030017ee28d5af7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 13:26:08 +0000 Subject: [PATCH 07/26] pre-commit auto-fixes --- pymatgen/io/lobster/outputs.py | 3 ++- .../io/lobster/outputs.py.BACKUP.16529.py | 4 +-- pymatgen/io/lobster/outputs.py.BASE.16529.py | 24 +++++++---------- pymatgen/io/lobster/outputs.py.LOCAL.16529.py | 26 ++++++++----------- tests/io/lobster/test_inputs.py | 4 ++- 5 files changed, 28 insertions(+), 33 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index f0614197e87..d567eedfcc1 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -46,6 +46,7 @@ description="Automated Bonding Analysis with Crystal Orbital Hamilton Populations", ) + class Cohpcar: """ Class to read COHPCAR/COOPCAR/COBICAR files generated by LOBSTER. @@ -488,7 +489,7 @@ def __init__(self, filename: str | None = None): numofatoms = str(line[1]) ncicobi[Spin.up] = float(line[2]) interactiontype = str(line[3:]).replace("'", "").replace(" ", "") - num = int(1) + num = 1 if self.is_spin_polarized: ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) diff --git a/pymatgen/io/lobster/outputs.py.BACKUP.16529.py b/pymatgen/io/lobster/outputs.py.BACKUP.16529.py index eb60bc17d89..828334f8e89 100644 --- a/pymatgen/io/lobster/outputs.py.BACKUP.16529.py +++ b/pymatgen/io/lobster/outputs.py.BACKUP.16529.py @@ -239,7 +239,7 @@ def _get_bond_data(line: str) -> dict: Dict with the bond label, the bond length, a tuple of the site indices, a tuple containing the orbitals (if orbital-resolved), and a label for the orbitals (if orbital-resolved). - """ + r""" line_new = line.rsplit("(", 1) length = float(line_new[-1][:-1]) @@ -414,7 +414,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s icohp = {} line = data_orb.split() label = f"{line[0]}" - orbs = re.findall(r"_(.*?)(?=\s)", data_orb) + orbs = re.findall(r"_(.*?)(?=\\s)", data_orb) orb_label, orbitals = get_orb_from_str(orbs) icohp[Spin.up] = float(line[7]) diff --git a/pymatgen/io/lobster/outputs.py.BASE.16529.py b/pymatgen/io/lobster/outputs.py.BASE.16529.py index 2eb02211079..c15b21afaa2 100644 --- a/pymatgen/io/lobster/outputs.py.BASE.16529.py +++ b/pymatgen/io/lobster/outputs.py.BASE.16529.py @@ -223,13 +223,12 @@ def _get_bond_data(line: str) -> dict: orbitals = None orb_label = None - bond_data = { + return { "length": length, "sites": site_indices, "orbitals": orbitals, "orb_label": orb_label, } - return bond_data class Icohplist: @@ -355,7 +354,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s length = float(line[3]) translation = [int(line[4]), int(line[5]), int(line[6])] icohp[Spin.up] = float(line[7]) - num = int(1) + num = 1 if self.is_spin_polarized: icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) @@ -648,7 +647,7 @@ def __init__(self, filename: str = "CHARGE.lobster"): self.types: list[str] = [] self.Mulliken: list[float] = [] self.Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): + for atom in range(self.num_atoms): line = data[atom].split() self.atomlist.append(line[1] + line[0]) self.types.append(line[1]) @@ -667,8 +666,7 @@ def get_structure_with_charges(self, structure_filename): Mulliken = self.Mulliken Loewdin = self.Loewdin site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - new_struct = struct.copy(site_properties=site_properties) - return new_struct + return struct.copy(site_properties=site_properties) class Lobsterout: @@ -1427,8 +1425,7 @@ def get_structure_with_total_grosspop(self, structure_filename: str) -> Structur "Total Mulliken GP": mullikengp, "Total Loewdin GP": loewdingp, } - new_struct = struct.copy(site_properties=site_properties) - return new_struct + return struct.copy(site_properties=site_properties) class Wavefunction: @@ -1519,9 +1516,9 @@ def set_volumetric_data(self, grid, structure): new_density = [] runner = 0 - for x in range(0, Nx + 1): - for y in range(0, Ny + 1): - for z in range(0, Nz + 1): + for x in range(Nx + 1): + for y in range(Ny + 1): + for z in range(Nz + 1): x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] @@ -1685,7 +1682,7 @@ def __init__(self, filename: str = "SitePotentials.lobster"): self.types: list[str] = [] self.sitepotentials_Mulliken: list[float] = [] self.sitepotentials_Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): + for atom in range(self.num_atoms): line = data[atom].split() self.atomlist.append(line[1] + str(line[0])) self.types.append(line[1]) @@ -1710,8 +1707,7 @@ def get_structure_with_site_potentials(self, structure_filename): "Mulliken Site Potentials (eV)": Mulliken, "Loewdin Site Potentials (eV)": Loewdin, } - new_struct = struct.copy(site_properties=site_properties) - return new_struct + return struct.copy(site_properties=site_properties) def get_orb_from_str(orbs): diff --git a/pymatgen/io/lobster/outputs.py.LOCAL.16529.py b/pymatgen/io/lobster/outputs.py.LOCAL.16529.py index 2ae04213f28..b0aa969ab96 100644 --- a/pymatgen/io/lobster/outputs.py.LOCAL.16529.py +++ b/pymatgen/io/lobster/outputs.py.LOCAL.16529.py @@ -223,13 +223,12 @@ def _get_bond_data(line: str) -> dict: orbitals = None orb_label = None - bond_data = { + return { "length": length, "sites": site_indices, "orbitals": orbitals, "orb_label": orb_label, } - return bond_data class Icohplist: @@ -355,7 +354,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s length = float(line[3]) translation = [int(line[4]), int(line[5]), int(line[6])] icohp[Spin.up] = float(line[7]) - num = int(1) + num = 1 if self.is_spin_polarized: icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) @@ -514,7 +513,7 @@ def __init__(self, filename: str | None = None): numofatoms = str(line[1]) ncicobi[Spin.up] = float(line[2]) interactiontype = str(line[3:]).replace("'", "").replace(" ", "") - num = int(1) + num = 1 if self.is_spin_polarized: ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) @@ -760,7 +759,7 @@ def __init__(self, filename: str = "CHARGE.lobster"): self.types: list[str] = [] self.Mulliken: list[float] = [] self.Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): + for atom in range(self.num_atoms): line = data[atom].split() self.atomlist.append(line[1] + line[0]) self.types.append(line[1]) @@ -779,8 +778,7 @@ def get_structure_with_charges(self, structure_filename): Mulliken = self.Mulliken Loewdin = self.Loewdin site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - new_struct = struct.copy(site_properties=site_properties) - return new_struct + return struct.copy(site_properties=site_properties) class Lobsterout: @@ -1539,8 +1537,7 @@ def get_structure_with_total_grosspop(self, structure_filename: str) -> Structur "Total Mulliken GP": mullikengp, "Total Loewdin GP": loewdingp, } - new_struct = struct.copy(site_properties=site_properties) - return new_struct + return struct.copy(site_properties=site_properties) class Wavefunction: @@ -1631,9 +1628,9 @@ def set_volumetric_data(self, grid, structure): new_density = [] runner = 0 - for x in range(0, Nx + 1): - for y in range(0, Ny + 1): - for z in range(0, Nz + 1): + for x in range(Nx + 1): + for y in range(Ny + 1): + for z in range(Nz + 1): x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] @@ -1797,7 +1794,7 @@ def __init__(self, filename: str = "SitePotentials.lobster"): self.types: list[str] = [] self.sitepotentials_Mulliken: list[float] = [] self.sitepotentials_Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): + for atom in range(self.num_atoms): line = data[atom].split() self.atomlist.append(line[1] + str(line[0])) self.types.append(line[1]) @@ -1822,8 +1819,7 @@ def get_structure_with_site_potentials(self, structure_filename): "Mulliken Site Potentials (eV)": Mulliken, "Loewdin Site Potentials (eV)": Loewdin, } - new_struct = struct.copy(site_properties=site_properties) - return new_struct + return struct.copy(site_properties=site_properties) def get_orb_from_str(orbs): diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index b7072eec618..c5d4b726407 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -696,7 +696,9 @@ def test_ncicobilist(self): assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobigz.ncicobilist["2"]["interaction_type"] - assert sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())) ==approx(self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up]) + assert sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())) == approx( + self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up] + ) class TestDoscar(unittest.TestCase): From 55a1b10728a1a551e46eb0d668ab63420dd94ad0 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Fri, 22 Sep 2023 15:31:25 +0200 Subject: [PATCH 08/26] cleaned up the merge mess --- .../io/lobster/outputs.py.BACKUP.16529.py | 1756 ---------------- pymatgen/io/lobster/outputs.py.BASE.16529.py | 1748 ---------------- pymatgen/io/lobster/outputs.py.LOCAL.16529.py | 1860 ----------------- .../io/lobster/outputs.py.REMOTE.16529.py | 1589 -------------- pymatgen/io/lobster/outputs.py.orig | 1756 ---------------- 5 files changed, 8709 deletions(-) delete mode 100644 pymatgen/io/lobster/outputs.py.BACKUP.16529.py delete mode 100644 pymatgen/io/lobster/outputs.py.BASE.16529.py delete mode 100644 pymatgen/io/lobster/outputs.py.LOCAL.16529.py delete mode 100644 pymatgen/io/lobster/outputs.py.REMOTE.16529.py delete mode 100644 pymatgen/io/lobster/outputs.py.orig diff --git a/pymatgen/io/lobster/outputs.py.BACKUP.16529.py b/pymatgen/io/lobster/outputs.py.BACKUP.16529.py deleted file mode 100644 index eb60bc17d89..00000000000 --- a/pymatgen/io/lobster/outputs.py.BACKUP.16529.py +++ /dev/null @@ -1,1756 +0,0 @@ -""" -Module for reading Lobster output files. For more information -on LOBSTER see www.cohp.de. -If you use this module, please cite: -J. George, G. Petretto, A. Naik, M. Esters, A. J. Jackson, R. Nelson, R. Dronskowski, G.-M. Rignanese, G. Hautier, -"Automated Bonding Analysis with Crystal Orbital Hamilton Populations", -ChemPlusChem 2022, e202200123, -DOI: 10.1002/cplu.202200123. -""" - -from __future__ import annotations - -import collections -import fnmatch -import os -import re -import warnings -from collections import defaultdict -from typing import TYPE_CHECKING, Any - -import numpy as np -from monty.io import zopen - -from pymatgen.core.structure import Structure -from pymatgen.electronic_structure.bandstructure import LobsterBandStructureSymmLine -from pymatgen.electronic_structure.core import Orbital, Spin -from pymatgen.electronic_structure.dos import Dos, LobsterCompleteDos -from pymatgen.io.vasp.inputs import Kpoints -from pymatgen.io.vasp.outputs import Vasprun, VolumetricData -from pymatgen.util.due import Doi, due - -if TYPE_CHECKING: - from pymatgen.core.structure import IStructure - -__author__ = "Janine George, Marco Esters" -__copyright__ = "Copyright 2017, The Materials Project" -__version__ = "0.2" -__maintainer__ = "Janine George " -__email__ = "janinegeorge.ulfen@gmail.com" -__date__ = "Dec 13, 2017" - -MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - -due.cite( - Doi("10.1002/cplu.202200123"), - description="Automated Bonding Analysis with Crystal Orbital Hamilton Populations", -) - -<<<<<<< HEAD -class Cohpcar: - """ - Class to read COHPCAR/COOPCAR/COBICAR files generated by LOBSTER. - - .. attribute: cohp_data - - Dict that contains the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have - a "length" key. - - .. attribute: efermi - - The Fermi energy in eV. - - .. attribute: energies - - Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - - .. attribute: is_spin_polarized - - Boolean to indicate if the calculation is spin polarized. - - .. attribute: orb_cohp - - orb_cohp[label] = {bond_data["orb_label"]: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}} -======= ->>>>>>> master - -class Cohpcar: - """Class to read COHPCAR/COOPCAR files generated by LOBSTER. - - Attributes: - cohp_data (dict[str, Dict[str, Any]]): A dictionary containing the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have a "length" key. - efermi (float): The Fermi energy in eV. - energies (Sequence[float]): Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - orb_cohp (dict[str, Dict[str, Dict[str, Any]]]): A dictionary containing the orbital-resolved COHPs of the form: - orb_cohp[label] = {bond_data["orb_label"]: { - "COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}, - } - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - are_cobis: Determines if the file is a list of COHPs or COBIs. - Default is False for COHPs. - - filename: Name of the COHPCAR file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "COOPCAR.lobster" - elif are_cobis: - filename = "COBICAR.lobster" - else: - filename = "COHPCAR.lobster" - - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - # The parameters line is the second line in a COHPCAR file. It - # contains all parameters that are needed to map the file. - parameters = contents[1].split() - # Subtract 1 to skip the average - num_bonds = int(parameters[0]) - 1 - self.efermi = float(parameters[-1]) - self.is_spin_polarized = int(parameters[1]) == 2 - spins = [Spin.up, Spin.down] if int(parameters[1]) == 2 else [Spin.up] - - # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] - cohp_data: dict[str, dict[str, Any]] = { - "average": { - "COHP": {spin: data[1 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - "ICOHP": {spin: data[2 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - } - } - - orb_cohp: dict[str, Any] = {} - # present for Lobster versions older than Lobster 2.2.0 - very_old = False - # the labeling had to be changed: there are more than one COHP for each atom combination - # this is done to make the labeling consistent with ICOHPLIST.lobster - bond_num = 0 - for bond in range(num_bonds): - bond_data = self._get_bond_data(contents[3 + bond]) - - label = str(bond_num) - - orbs = bond_data["orbitals"] - cohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 3] for s, spin in enumerate(spins)} - - icohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 4] for s, spin in enumerate(spins)} - if orbs is None: - bond_num = bond_num + 1 - label = str(bond_num) - cohp_data[label] = { - "COHP": cohp, - "ICOHP": icohp, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - ) - else: - # present for Lobster versions older than Lobster 2.2.0 - if bond_num == 0: - very_old = True - if very_old: - bond_num += 1 - label = str(bond_num) - - orb_cohp[label] = { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - - # present for lobster older than 2.2.0 - if very_old: - for bond_str in orb_cohp: - cohp_data[bond_str] = { - "COHP": None, - "ICOHP": None, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - self.orb_res_cohp = orb_cohp or None - self.cohp_data = cohp_data - - @staticmethod - def _get_bond_data(line: str) -> dict: - """ - Subroutine to extract bond label, site indices, and length from - a LOBSTER header line. The site indices are zero-based, so they - can be easily used with a Structure object. - - Example header line: No.4:Fe1->Fe9(2.4524893531900283) - Example header line for orbtial-resolved COHP: - No.1:Fe1[3p_x]->Fe2[3d_x^2-y^2](2.456180552772262) - - Args: - line: line in the COHPCAR header describing the bond. - - Returns: - Dict with the bond label, the bond length, a tuple of the site - indices, a tuple containing the orbitals (if orbital-resolved), - and a label for the orbitals (if orbital-resolved). - """ - line_new = line.rsplit("(", 1) - length = float(line_new[-1][:-1]) - - sites = line_new[0].replace("->", ":").split(":")[1:3] - site_indices = tuple(int(re.split(r"\D+", site)[1]) - 1 for site in sites) - - if "[" in sites[0]: - orbs = [re.findall(r"\[(.*)\]", site)[0] for site in sites] - orb_label, orbitals = get_orb_from_str(orbs) - - else: - orbitals = orb_label = None - - return { - "length": length, - "sites": site_indices, - "orbitals": orbitals, - "orb_label": orb_label, - } - - -class Icohplist: - """ - Class to read ICOHPLIST/ICOOPLIST files generated by LOBSTER. - -<<<<<<< HEAD - .. attribute: are_coops - Boolean to indicate if the populations are COOPs, COHPs or COBIs. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Icohplist - Dict containing the listfile data of the form: - {bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...}} - - .. attribute: IcohpCollection - IcohpCollection Object - -======= - Attributes: - are_coops (bool): Indicates whether the object is consisting of COOPs. - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - Icohplist (dict[str, Dict[str, Union[float, int, Dict[Spin, float]]]]): Dict containing the - listfile data of the form: { - bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...} - } - IcohpCollection (IcohpCollection): IcohpCollection Object. ->>>>>>> master - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of ICOOPs. - Defaults to False for ICOHPs. - are_cobis: Determines if the file is a list of ICOBIs. - Defaults to False for ICOHPs. - filename: Name of the ICOHPLIST file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "ICOOPLIST.lobster" - elif are_cobis: - filename = "ICOBILIST.lobster" - else: - filename = "ICOHPLIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("ICOHPLIST file contains no data.") - - # Which Lobster version? - if len(data[0].split()) == 8: - version = "3.1.1" - elif len(data[0].split()) == 6: - version = "2.2.1" - warnings.warn("Please consider using the new Lobster version. See www.cohp.de.") - else: - raise ValueError - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = "distance" in data[len(data) // 2] - - # check if orbitalwise ICOHPLIST - # include case when there is only one ICOHP!!! - self.orbitalwise = len(data) > 2 and "_" in data[1].split()[1] - - if self.orbitalwise: - data_without_orbitals = [] - data_orbitals = [] - for line in data: - if "_" not in line.split()[1]: - data_without_orbitals.append(line) - else: - data_orbitals.append(line) - - else: - data_without_orbitals = data - - if "distance" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("ICOHPLIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - list_labels = [] - list_atom1 = [] - list_atom2 = [] - list_length = [] - list_translation = [] - list_num = [] - list_icohp = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - icohp = {} - if version == "2.2.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - icohp[Spin.up] = float(line[4]) - num = int(line[5]) - translation = [0, 0, 0] - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[4]) - - elif version == "3.1.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - translation = [int(line[4]), int(line[5]), int(line[6])] - icohp[Spin.up] = float(line[7]) - num = 1 - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) - - list_labels.append(label) - list_atom1.append(atom1) - list_atom2.append(atom2) - list_length.append(length) - list_translation.append(translation) - list_num.append(num) - list_icohp.append(icohp) - - list_orb_icohp: list[dict] | None = None - if self.orbitalwise: - list_orb_icohp = [] - num_orbs = len(data_orbitals) // 2 if self.is_spin_polarized else len(data_orbitals) - - for i_data_orb in range(num_orbs): - data_orb = data_orbitals[i_data_orb] - icohp = {} - line = data_orb.split() - label = f"{line[0]}" - orbs = re.findall(r"_(.*?)(?=\s)", data_orb) - orb_label, orbitals = get_orb_from_str(orbs) - icohp[Spin.up] = float(line[7]) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_orbitals[num_orbs + i_data_orb].split()[7]) - - if len(list_orb_icohp) < int(label): - list_orb_icohp.append({orb_label: {"icohp": icohp, "orbitals": orbitals}}) - else: - list_orb_icohp[int(label) - 1][orb_label] = {"icohp": icohp, "orbitals": orbitals} - - # to avoid circular dependencies - from pymatgen.electronic_structure.cohp import IcohpCollection - - self._icohpcollection = IcohpCollection( - are_coops=are_coops, - are_cobis=are_cobis, - list_labels=list_labels, - list_atom1=list_atom1, - list_atom2=list_atom2, - list_length=list_length, - list_translation=list_translation, - list_num=list_num, - list_icohp=list_icohp, - is_spin_polarized=self.is_spin_polarized, - list_orb_icohp=list_orb_icohp, - ) - - @property - def icohplist(self) -> dict[Any, dict[str, Any]]: - """Returns: icohplist compatible with older version of this class.""" - icohplist_new = {} - for key, value in self._icohpcollection._icohplist.items(): - icohplist_new[key] = { - "length": value._length, - "number_of_bonds": value._num, - "icohp": value._icohp, - "translation": value._translation, - "orbitals": value._orbitals, - } - return icohplist_new - - @property - def icohpcollection(self): - """Returns: IcohpCollection object.""" - return self._icohpcollection - - -class Ncicobilist: - """ - Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Ncicobilist - Dict containing the listfile data of the form: - {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, - "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, - "interaction_type": type of the multi-center interaction - - """ - - def __init__(self, filename: str | None = None): - """ - Args: - filename: Name of the NcICOBILIST file. - """ - - if filename is None: - warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") - filename = "NcICOBILIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("NcICOBILIST file contains no data.") - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - if "spin" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = True - else: - self.is_spin_polarized = False - - # check if orbitalwise NcICOBILIST - # include case when there is only one NcICOBI - for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed - if len(data) > 2 and "s]" in str(entry.split()[3:]): - self.orbitalwise = True - warnings.warn( - "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " - + "information is not read!" - ) - break # condition has only to be met once - else: - self.orbitalwise = False - - if self.orbitalwise: - data_without_orbitals = [] - for line in data: - if "_" not in str(line.split()[3:]) and "s]" not in str(line.split()[3:]): - data_without_orbitals.append(line) - else: - data_without_orbitals = data - - if "spin" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("NcICOBILIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - self.list_labels = [] - self.list_numofatoms = [] - self.list_ncicobi = [] - self.list_interactiontype = [] - self.list_num = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - ncicobi = {} - - label = f"{line[0]}" - numofatoms = str(line[1]) - ncicobi[Spin.up] = float(line[2]) - interactiontype = str(line[3:]).replace("'", "").replace(" ", "") - num = int(1) - - if self.is_spin_polarized: - ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) - - self.list_labels.append(label) - self.list_numofatoms.append(numofatoms) - self.list_ncicobi.append(ncicobi) - self.list_interactiontype.append(interactiontype) - self.list_num.append(num) - - # TODO: add functions to get orbital resolved NcICOBIs - - @property - def ncicobilist(self) -> dict[Any, dict[str, Any]]: - """ - Returns: ncicobilist. - """ - ncicobilist = {} - for key, _entry in enumerate(self.list_labels): - ncicobilist[str(key + 1)] = { - "number_of_atoms": int(self.list_numofatoms[key]), - "ncicobi": self.list_ncicobi[key], - "interaction_type": self.list_interactiontype[key], - } - - return ncicobilist - - -class Doscar: - """ - Class to deal with Lobster's projected DOS and local projected DOS. - The beforehand quantum-chemical calculation was performed with VASP. - - Attributes: - completedos (LobsterCompleteDos): LobsterCompleteDos Object. - pdos (list): List of Dict including numpy arrays with pdos. Access as - pdos[atomindex]['orbitalstring']['Spin.up/Spin.down']. - tdos (Dos): Dos Object of the total density of states. - energies (numpy.ndarray): Numpy array of the energies at which the DOS was calculated - (in eV, relative to Efermi). - tdensities (dict): tdensities[Spin.up]: numpy array of the total density of states for - the Spin.up contribution at each of the energies. tdensities[Spin.down]: numpy array - of the total density of states for the Spin.down contribution at each of the energies. - If is_spin_polarized=False, tdensities[Spin.up]: numpy array of the total density of states. - itdensities (dict): itdensities[Spin.up]: numpy array of the total density of states for - the Spin.up contribution at each of the energies. itdensities[Spin.down]: numpy array - of the total density of states for the Spin.down contribution at each of the energies. - If is_spin_polarized=False, itdensities[Spin.up]: numpy array of the total density of states. - is_spin_polarized (bool): Boolean. Tells if the system is spin polarized. - """ - - def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str | None = "POSCAR", - structure: IStructure | Structure | None = None, - ): - """ - Args: - doscar: DOSCAR filename, typically "DOSCAR.lobster" - structure_file: for vasp, this is typically "POSCAR" - structure: instead of a structure file, the structure can be given - directly. structure_file will be preferred. - """ - self._doscar = doscar - - self._final_structure = Structure.from_file(structure_file) if structure_file is not None else structure - - self._parse_doscar() - - def _parse_doscar(self): - doscar = self._doscar - - tdensities = {} - itdensities = {} - with zopen(doscar, "rt") as f: - natoms = int(f.readline().split()[0]) - efermi = float([f.readline() for nn in range(4)][3].split()[17]) - dos = [] - orbitals = [] - for _atom in range(natoms + 1): - line = f.readline() - ndos = int(line.split()[2]) - orbitals.append(line.split(";")[-1].split()) - line = f.readline().split() - cdos = np.zeros((ndos, len(line))) - cdos[0] = np.array(line) - for nd in range(1, ndos): - line = f.readline().split() - cdos[nd] = np.array(line) - dos.append(cdos) - doshere = np.array(dos[0]) - if len(doshere[0, :]) == 5: - self._is_spin_polarized = True - elif len(doshere[0, :]) == 3: - self._is_spin_polarized = False - else: - raise ValueError("There is something wrong with the DOSCAR. Can't extract spin polarization.") - energies = doshere[:, 0] - if not self._is_spin_polarized: - tdensities[Spin.up] = doshere[:, 1] - itdensities[Spin.up] = doshere[:, 2] - pdoss = [] - spin = Spin.up - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - orbnumber = orbnumber + 1 - pdoss.append(pdos) - else: - tdensities[Spin.up] = doshere[:, 1] - tdensities[Spin.down] = doshere[:, 2] - itdensities[Spin.up] = doshere[:, 3] - itdensities[Spin.down] = doshere[:, 4] - pdoss = [] - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - spin = Spin.down if j % 2 == 0 else Spin.up - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - if j % 2 == 0: - orbnumber = orbnumber + 1 - pdoss.append(pdos) - - self._efermi = efermi - self._pdos = pdoss - self._tdos = Dos(efermi, energies, tdensities) - self._energies = energies - self._tdensities = tdensities - self._itdensities = itdensities - final_struct = self._final_structure - - pdossneu = {final_struct[i]: pdos for i, pdos in enumerate(self._pdos)} - - self._completedos = LobsterCompleteDos(final_struct, self._tdos, pdossneu) - - @property - def completedos(self) -> LobsterCompleteDos: - """LobsterCompleteDos""" - return self._completedos - - @property - def pdos(self) -> list: - """Projected DOS""" - return self._pdos - - @property - def tdos(self) -> Dos: - """Total DOS""" - return self._tdos - - @property - def energies(self) -> np.ndarray: - """Energies""" - return self._energies - - @property - def tdensities(self) -> np.ndarray: - """total densities as a np.ndarray""" - return self._tdensities - - @property - def itdensities(self) -> np.ndarray: - """integrated total densities as a np.ndarray""" - return self._itdensities - - @property - def is_spin_polarized(self) -> bool: - """Whether run is spin polarized.""" - return self._is_spin_polarized - - -class Charge: - """ - Class to read CHARGE files generated by LOBSTER. - - Attributes: - atomlist (list[str]): List of atoms in CHARGE.lobster. - types (list[str]): List of types of atoms in CHARGE.lobster. - Mulliken (list[float]): List of Mulliken charges of atoms in CHARGE.lobster. - Loewdin (list[float]): List of Loewdin charges of atoms in CHARGE.Loewdin. - num_atoms (int): Number of atoms in CHARGE.lobster. - """ - - def __init__(self, filename: str = "CHARGE.lobster"): - """ - Args: - filename: filename for the CHARGE file, typically "CHARGE.lobster". - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[3:-3] - if len(data) == 0: - raise OSError("CHARGES file contains no data.") - - self.num_atoms = len(data) - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.Mulliken: list[float] = [] - self.Loewdin: list[float] = [] - for atom in range(self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + line[0]) - self.types.append(line[1]) - self.Mulliken.append(float(line[2])) - self.Loewdin.append(float(line[3])) - - def get_structure_with_charges(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - - Args: - structure_filename: filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin charges as site properties. - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.Mulliken - Loewdin = self.Loewdin - site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - return struct.copy(site_properties=site_properties) - - -class Lobsterout: - """ - Class to read in the lobsterout and evaluate the spilling, save the basis, save warnings, save infos. - - Attributes: - basis_functions (list[str]): List of basis functions that were used in lobster run as strings. - basis_type (list[str]): List of basis type that were used in lobster run as strings. - charge_spilling (list[float]): List of charge spilling (first entry: result for spin 1, - second entry: result for spin 2 or not present). - dft_program (str): String representing the DFT program used for the calculation of the wave function. - elements (list[str]): List of strings of elements that were present in lobster calculation. - has_charge (bool): Whether CHARGE.lobster is present. - has_cohpcar (bool): Whether COHPCAR.lobster and ICOHPLIST.lobster are present. - has_madelung (bool): Whether SitePotentials.lobster and MadelungEnergies.lobster are present. - has_coopcar (bool): Whether COOPCAR.lobster and ICOOPLIST.lobster are present. - has_cobicar (bool): Whether COBICAR.lobster and ICOBILIST.lobster are present. - has_doscar (bool): Whether DOSCAR.lobster is present. - has_doscar_lso (bool): Whether DOSCAR.LSO.lobster is present. - has_projection (bool): Whether projectionData.lobster is present. - has_bandoverlaps (bool): Whether bandOverlaps.lobster is present. - has_density_of_energies (bool): Whether DensityOfEnergy.lobster is present. - has_fatbands (bool): Whether fatband calculation was performed. - has_grosspopulation (bool): Whether GROSSPOP.lobster is present. - info_lines (str): String with additional infos on the run. - info_orthonormalization (str): String with infos on orthonormalization. - is_restart_from_projection (bool): Boolean that indicates that calculation was restarted - from existing projection file. - lobster_version (str): String that indicates Lobster version. - number_of_spins (int): Integer indicating the number of spins. - number_of_threads (int): Integer that indicates how many threads were used. - timing (dict[str, float]): Dictionary with infos on timing. - total_spilling (list[float]): List of values indicating the total spilling for spin - channel 1 (and spin channel 2). - warning_lines (str): String with all warnings. - """ - - # TODO: add tests for skipping COBI and madelung - # TODO: add tests for including COBI and madelung - def __init__(self, filename="lobsterout"): - """ - Args: - filename: filename of lobsterout. - """ - # read in file - with zopen(filename, "rt") as f: - data = f.read().split("\n") # [3:-3] - if len(data) == 0: - raise OSError("lobsterout does not contain any data") - - # check if Lobster starts from a projection - self.is_restart_from_projection = "loading projection from projectionData.lobster..." in data - - self.lobster_version = self._get_lobster_version(data=data) - - self.number_of_threads = int(self._get_threads(data=data)) - self.dft_program = self._get_dft_program(data=data) - - self.number_of_spins = self._get_number_of_spins(data=data) - chargespilling, totalspilling = self._get_spillings(data=data, number_of_spins=self.number_of_spins) - self.charge_spilling = chargespilling - self.total_spilling = totalspilling - - elements, basistype, basisfunctions = self._get_elements_basistype_basisfunctions(data=data) - self.elements = elements - self.basis_type = basistype - self.basis_functions = basisfunctions - - wall_time, user_time, sys_time = self._get_timing(data=data) - timing = {} - timing["wall_time"] = wall_time - timing["user_time"] = user_time - timing["sys_time"] = sys_time - self.timing = timing - - warninglines = self._get_all_warning_lines(data=data) - self.warning_lines = warninglines - - orthowarning = self._get_warning_orthonormalization(data=data) - self.info_orthonormalization = orthowarning - - infos = self._get_all_info_lines(data=data) - self.info_lines = infos - - self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data - self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data - ) - self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data - ) - self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data - ) - self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data - ) - - self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data - self.has_projection = "saving projection to projectionData.lobster..." in data - self.has_bandoverlaps = "WARNING: I dumped the band overlap matrices to the file bandOverlaps.lobster." in data - self.has_fatbands = self._has_fatband(data=data) - self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data - self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data - self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data - ) - - def get_doc(self): - """Returns: LobsterDict with all the information stored in lobsterout.""" - LobsterDict = {} - # check if Lobster starts from a projection - LobsterDict["restart_from_projection"] = self.is_restart_from_projection - LobsterDict["lobster_version"] = self.lobster_version - LobsterDict["threads"] = self.number_of_threads - LobsterDict["dft_program"] = self.dft_program - - LobsterDict["charge_spilling"] = self.charge_spilling - LobsterDict["total_spilling"] = self.total_spilling - - LobsterDict["elements"] = self.elements - LobsterDict["basis_type"] = self.basis_type - LobsterDict["basis_functions"] = self.basis_functions - - LobsterDict["timing"] = self.timing - - LobsterDict["warning_lines"] = self.warning_lines - - LobsterDict["info_orthonormalization"] = self.info_orthonormalization - - LobsterDict["info_lines"] = self.info_lines - - LobsterDict["has_doscar"] = self.has_doscar - LobsterDict["has_doscar_lso"] = self.has_doscar_lso - LobsterDict["has_cohpcar"] = self.has_cohpcar - LobsterDict["has_coopcar"] = self.has_coopcar - LobsterDict["has_cobicar"] = self.has_cobicar - LobsterDict["has_charge"] = self.has_charge - LobsterDict["has_madelung"] = self.has_madelung - LobsterDict["has_projection"] = self.has_projection - LobsterDict["has_bandoverlaps"] = self.has_bandoverlaps - LobsterDict["has_fatbands"] = self.has_fatbands - LobsterDict["has_grosspopulation"] = self.has_grosspopulation - LobsterDict["has_density_of_energies"] = self.has_density_of_energies - - return LobsterDict - - @staticmethod - def _get_lobster_version(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[0] == "LOBSTER": - return splitrow[1] - raise RuntimeError("Version not found.") - - @staticmethod - def _has_fatband(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[1] == "FatBand": - return True - return False - - @staticmethod - def _get_dft_program(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 4 and splitrow[3] == "program...": - return splitrow[4] - return None - - @staticmethod - def _get_number_of_spins(data): - if "spillings for spin channel 2" in data: - return 2 - return 1 - - @staticmethod - def _get_threads(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): - return splitrow[10] - raise ValueError("Threads not found.") - - @staticmethod - def _get_spillings(data, number_of_spins): - charge_spilling = [] - total_spilling = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 2 and splitrow[2] == "spilling:": - if splitrow[1] == "charge": - charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - if splitrow[1] == "total": - total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - - if len(charge_spilling) == number_of_spins and len(total_spilling) == number_of_spins: - break - - return charge_spilling, total_spilling - - @staticmethod - def _get_elements_basistype_basisfunctions(data): - begin = False - end = False - elements = [] - basistype = [] - basisfunctions = [] - for row in data: - if begin and not end: - splitrow = row.split() - if splitrow[0] not in [ - "INFO:", - "WARNING:", - "setting", - "calculating", - "post-processing", - "saving", - "spillings", - "writing", - ]: - elements.append(splitrow[0]) - basistype.append(splitrow[1].replace("(", "").replace(")", "")) - # last sign is a '' - basisfunctions.append(splitrow[2:]) - else: - end = True - if "setting up local basis functions..." in row: - begin = True - return elements, basistype, basisfunctions - - @staticmethod - def _get_timing(data): - # will give back wall, user and sys time - begin = False - # end=False - # time=[] - - for row in data: - splitrow = row.split() - if "finished" in splitrow: - begin = True - if begin: - if "wall" in splitrow: - wall_time = splitrow[2:10] - if "user" in splitrow: - user_time = splitrow[0:8] - if "sys" in splitrow: - sys_time = splitrow[0:8] - - wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} - user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} - sys_time_dict = {"h": sys_time[0], "min": sys_time[2], "s": sys_time[4], "ms": sys_time[6]} - - return wall_time_dict, user_time_dict, sys_time_dict - - @staticmethod - def _get_warning_orthonormalization(data): - orthowarning = [] - for row in data: - splitrow = row.split() - if "orthonormalized" in splitrow: - orthowarning.append(" ".join(splitrow[1:])) - return orthowarning - - @staticmethod - def _get_all_warning_lines(data): - ws = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "WARNING:": - ws.append(" ".join(splitrow[1:])) - return ws - - @staticmethod - def _get_all_info_lines(data): - infos = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "INFO:": - infos.append(" ".join(splitrow[1:])) - return infos - - -class Fatband: - """ - Reads in FATBAND_x_y.lobster files. - - Attributes: - efermi (float): Fermi energy read in from vasprun.xml. - eigenvals (dict[Spin, np.ndarray]): Eigenvalues as a dictionary of numpy arrays of shape (nbands, nkpoints). - The first index of the array refers to the band and the second to the index of the kpoint. - The kpoints are ordered according to the order of the kpoints_array attribute. - If the band structure is not spin polarized, we only store one data set under Spin.up. - is_spin_polarized (bool): Boolean that tells you whether this was a spin-polarized calculation. - kpoints_array (list[np.ndarray]): List of kpoints as numpy arrays, in frac_coords of the given - lattice by default. - label_dict (dict[str, Union[str, np.ndarray]]): Dictionary that links a kpoint (in frac coords or Cartesian - coordinates depending on the coords attribute) to a label. - lattice (Lattice): Lattice object of reciprocal lattice as read in from vasprun.xml. - nbands (int): Number of bands used in the calculation. - p_eigenvals (dict[Spin, np.ndarray]): Dictionary of orbital projections as {spin: array of dict}. - The indices of the array are [band_index, kpoint_index]. - The dict is then built the following way: {"string of element": "string of orbital as read in - from FATBAND file"}. If the band structure is not spin polarized, we only store one data set under Spin.up. - structure (Structure): Structure read in from vasprun.xml. - """ - - def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): - """ - Args: - filenames (list or string): can be a list of file names or a path to a folder from which all - "FATBAND_*" files will be read - vasprun: corresponding vasprun file - Kpointsfile: KPOINTS file for bandstructure calculation, typically "KPOINTS". - """ - warnings.warn("Make sure all relevant FATBAND files were generated and read in!") - warnings.warn("Use Lobster 3.2.0 or newer for fatband calculations!") - - vasp_run = Vasprun( - filename=vasprun, - ionic_step_skip=None, - ionic_step_offset=0, - parse_dos=True, - parse_eigen=False, - parse_projected_eigen=False, - parse_potcar_file=False, - occu_tol=1e-8, - exception_on_bad_xml=True, - ) - self.structure = vasp_run.final_structure - self.lattice = self.structure.lattice.reciprocal_lattice - self.efermi = vasp_run.efermi - kpoints_object = Kpoints.from_file(Kpointsfile) - - atomtype = [] - atomnames = [] - orbital_names = [] - - if not isinstance(filenames, list) or filenames is None: - filenames_new = [] - if filenames is None: - filenames = "." - for file in os.listdir(filenames): - if fnmatch.fnmatch(file, "FATBAND_*.lobster"): - filenames_new.append(os.path.join(filenames, file)) - filenames = filenames_new - if len(filenames) == 0: - raise ValueError("No FATBAND files in folder or given") - for filename in filenames: - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - atomnames.append(os.path.split(filename)[1].split("_")[1].capitalize()) - parameters = contents[0].split() - atomtype.append(re.split(r"[0-9]+", parameters[3])[0].capitalize()) - orbital_names.append(parameters[4]) - - # get atomtype orbital dict - atom_orbital_dict = {} - for iatom, atom in enumerate(atomnames): - if atom not in atom_orbital_dict: - atom_orbital_dict[atom] = [] - atom_orbital_dict[atom].append(orbital_names[iatom]) - # test if there are the same orbitals twice or if two different formats were used or if all necessary orbitals - # are there - for items in atom_orbital_dict.values(): - if len(set(items)) != len(items): - raise ValueError("The are two FATBAND files for the same atom and orbital. The program will stop.") - split = [] - for item in items: - split.append(item.split("_")[0]) - for number in collections.Counter(split).values(): - if number not in (1, 3, 5, 7): - raise ValueError( - "Make sure all relevant orbitals were generated and that no duplicates (2p and 2p_x) are " - "present" - ) - - kpoints_array = [] - for ifilename, filename in enumerate(filenames): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - if ifilename == 0: - self.nbands = int(parameters[6]) - self.number_kpts = kpoints_object.num_kpts - int(contents[1].split()[2]) + 1 - - if len(contents[1:]) == self.nbands + 2: - self.is_spinpolarized = False - elif len(contents[1:]) == self.nbands * 2 + 2: - self.is_spinpolarized = True - else: - linenumbers = [] - for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): - if line.split()[0] == "#": - linenumbers.append(iline) - - if ifilename == 0: - self.is_spinpolarized = len(linenumbers) == 2 - - if ifilename == 0: - eigenvals = {} - eigenvals[Spin.up] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - if self.is_spinpolarized: - eigenvals[Spin.down] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - - p_eigenvals = {} - p_eigenvals[Spin.up] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - if self.is_spinpolarized: - p_eigenvals[Spin.down] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - ikpoint = -1 - for line in contents[1:-1]: - if line.split()[0] == "#": - KPOINT = np.array( - [ - float(line.split()[4]), - float(line.split()[5]), - float(line.split()[6]), - ] - ) - if ifilename == 0: - kpoints_array.append(KPOINT) - - linenumber = 0 - iband = 0 - ikpoint += 1 - if linenumber == self.nbands: - iband = 0 - if line.split()[0] != "#": - if linenumber < self.nbands: - if ifilename == 0: - eigenvals[Spin.up][iband][ikpoint] = float(line.split()[1]) + self.efermi - - p_eigenvals[Spin.up][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - if linenumber >= self.nbands and self.is_spinpolarized: - if ifilename == 0: - eigenvals[Spin.down][iband][ikpoint] = float(line.split()[1]) + self.efermi - p_eigenvals[Spin.down][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - - linenumber += 1 - iband += 1 - - self.kpoints_array = kpoints_array - self.eigenvals = eigenvals - self.p_eigenvals = p_eigenvals - - label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): - if label is not None: - label_dict[label] = kpoints_array[ilabel] - - self.label_dict = label_dict - - def get_bandstructure(self): - """Returns a LobsterBandStructureSymmLine object which can be plotted with a normal BSPlotter.""" - return LobsterBandStructureSymmLine( - kpoints=self.kpoints_array, - eigenvals=self.eigenvals, - lattice=self.lattice, - efermi=self.efermi, - labels_dict=self.label_dict, - structure=self.structure, - projections=self.p_eigenvals, - ) - - -class Bandoverlaps: - """ - Class to read in bandOverlaps.lobster files. These files are not created during every Lobster run. - Attributes: - bandoverlapsdict (dict[Spin, Dict[str, Dict[str, Union[float, np.ndarray]]]]): A dictionary - containing the band overlap data of the form: {spin: {"kpoint as string": {"maxDeviation": - float that describes the max deviation, "matrix": 2D array of the size number of bands - times number of bands including the overlap matrices with}}}. - maxDeviation (list[float]): A list of floats describing the maximal deviation for each problematic kpoint. - """ - - def __init__(self, filename: str = "bandOverlaps.lobster"): - """ - Args: - filename: filename of the "bandOverlaps.lobster" file. - """ - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - spin_numbers = [0, 1] if contents[0].split()[-1] == "0" else [1, 2] - - self._read(contents, spin_numbers) - - def _read(self, contents: list, spin_numbers: list): - """ - Will read in all contents of the file - - Args: - contents: list of strings - spin_numbers: list of spin numbers depending on `Lobster` version. - """ - self.bandoverlapsdict: dict[Any, dict] = {} # Any is spin number 1 or -1 - self.max_deviation = [] - # This has to be done like this because there can be different numbers of problematic k-points per spin - for line in contents: - if f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[0]}" in line: - spin = Spin.up - elif f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[1]}" in line: - spin = Spin.down - elif "k-point" in line: - kpoint = line.split(" ") - kpoint_array = [] - for kpointel in kpoint: - if kpointel not in ["at", "k-point", ""]: - kpoint_array.append(str(kpointel)) - - elif "maxDeviation" in line: - if spin not in self.bandoverlapsdict: - self.bandoverlapsdict[spin] = {} - if " ".join(kpoint_array) not in self.bandoverlapsdict[spin]: - self.bandoverlapsdict[spin][" ".join(kpoint_array)] = {} - maxdev = line.split(" ")[2] - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["maxDeviation"] = float(maxdev) - self.max_deviation.append(float(maxdev)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"] = [] - - else: - overlaps = [] - for el in line.split(" "): - if el not in [""]: - overlaps.append(float(el)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"].append(overlaps) - - def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool: - """ - Will check if the maxDeviation from the ideal bandoverlap is smaller or equal to limit_maxDeviation - - Args: - limit_maxDeviation: limit of the maxDeviation - - Returns: - Boolean that will give you information about the quality of the projection. - """ - return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) - - def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, - ) -> bool: - """ - Will check if the deviation from the ideal bandoverlap of all occupied bands - is smaller or equal to limit_deviation. - - Args: - number_occ_bands_spin_up (int): number of occupied bands of spin up - number_occ_bands_spin_down (int): number of occupied bands of spin down - spin_polarized (bool): If True, then it was a spin polarized calculation - limit_deviation (float): limit of the maxDeviation - - Returns: - Boolean that will give you information about the quality of the projection - """ - for matrix in self.bandoverlapsdict[Spin.up].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if iband1 < number_occ_bands_spin_up and iband2 < number_occ_bands_spin_up: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - - if spin_polarized: - for matrix in self.bandoverlapsdict[Spin.down].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if number_occ_bands_spin_down is not None: - if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - else: - ValueError("number_occ_bands_spin_down has to be specified") - return True - - -class Grosspop: - """ - Class to read in GROSSPOP.lobster files. - - Attributes: - list_dict_grosspop (list[dict[str, str| dict[str, str]]]): List of dictionaries - including all information about the grosspopulations. Each dictionary contains the following keys: - - 'element': The element symbol of the atom. - - 'Mulliken GP': A dictionary of Mulliken gross populations, where the keys are the orbital labels and the - values are the corresponding gross populations as strings. - - 'Loewdin GP': A dictionary of Loewdin gross populations, where the keys are the orbital labels and the - values are the corresponding gross populations as strings. - The 0th entry of the list refers to the first atom in GROSSPOP.lobster and so on. - """ - - def __init__(self, filename: str = "GROSSPOP.lobster"): - """ - Args: - filename: filename of the "GROSSPOP.lobster" file. - """ - # opens file - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - self.list_dict_grosspop = [] - # transfers content of file to list of dict - for line in contents[3:]: - cleanline = [i for i in line.split(" ") if i != ""] - if len(cleanline) == 5: - small_dict = {} - small_dict["element"] = cleanline[1] - small_dict["Mulliken GP"] = {} - small_dict["Loewdin GP"] = {} - small_dict["Mulliken GP"][cleanline[2]] = float(cleanline[3]) - small_dict["Loewdin GP"][cleanline[2]] = float(cleanline[4]) - elif len(cleanline) > 0: - small_dict["Mulliken GP"][cleanline[0]] = float(cleanline[1]) - small_dict["Loewdin GP"][cleanline[0]] = float(cleanline[2]) - if "total" in cleanline[0]: - self.list_dict_grosspop.append(small_dict) - - def get_structure_with_total_grosspop(self, structure_filename: str) -> Structure: - """ - Get a Structure with Mulliken and Loewdin total grosspopulations as site properties - - Args: - structure_filename (str): filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin total grosspopulations as site properties. - """ - struct = Structure.from_file(structure_filename) - site_properties: dict[str, Any] = {} - mullikengp = [] - loewdingp = [] - for grosspop in self.list_dict_grosspop: - mullikengp.append(grosspop["Mulliken GP"]["total"]) - loewdingp.append(grosspop["Loewdin GP"]["total"]) - - site_properties = { - "Total Mulliken GP": mullikengp, - "Total Loewdin GP": loewdingp, - } - return struct.copy(site_properties=site_properties) - - -class Wavefunction: - """ - Class to read in wave function files from Lobster and transfer them into an object of the type VolumetricData. - - Attributes: - grid (tuple[int, int, int]): Grid for the wave function [Nx+1,Ny+1,Nz+1]. - points (list[Tuple[float, float, float]]): List of points. - real (list[float]): List of real part of wave function. - imaginary (list[float]): List of imaginary part of wave function. - distance (list[float]): List of distance to first point in wave function file. - """ - - def __init__(self, filename, structure): - """ - Args: - filename: filename of wavecar file from Lobster - structure: Structure object (e.g., created by Structure.from_file("")). - """ - self.filename = filename - self.structure = structure - self.grid, self.points, self.real, self.imaginary, self.distance = Wavefunction._parse_file(filename) - - @staticmethod - def _parse_file(filename): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - points = [] - distance = [] - real = [] - imaginary = [] - splitline = contents[0].split() - grid = [int(splitline[7]), int(splitline[8]), int(splitline[9])] - for line in contents[1:]: - splitline = line.split() - if len(splitline) >= 6: - points.append([float(splitline[0]), float(splitline[1]), float(splitline[2])]) - distance.append(float(splitline[3])) - real.append(float(splitline[4])) - imaginary.append(float(splitline[5])) - - if not len(real) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - if not len(imaginary) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - return grid, points, real, imaginary, distance - - def set_volumetric_data(self, grid, structure): - """ - Will create the VolumetricData Objects. - - Args: - grid: grid on which wavefunction was calculated, e.g. [1,2,2] - structure: Structure object - """ - Nx = grid[0] - 1 - Ny = grid[1] - 1 - Nz = grid[2] - 1 - a = structure.lattice.matrix[0] - b = structure.lattice.matrix[1] - c = structure.lattice.matrix[2] - new_x = [] - new_y = [] - new_z = [] - new_real = [] - new_imaginary = [] - new_density = [] - - runner = 0 - for x in range(Nx + 1): - for y in range(Ny + 1): - for z in range(Nz + 1): - x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] - y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] - z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - - if x != Nx and y != Ny and z != Nz: - if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) - ): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x.append(x_here) - new_y.append(y_here) - new_z.append(z_here) - - new_real.append(self.real[runner]) - new_imaginary.append(self.imaginary[runner]) - new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) - - runner += 1 - - self.final_real = np.reshape(new_real, [Nx, Ny, Nz]) - self.final_imaginary = np.reshape(new_imaginary, [Nx, Ny, Nz]) - self.final_density = np.reshape(new_density, [Nx, Ny, Nz]) - - self.volumetricdata_real = VolumetricData(structure, {"total": self.final_real}) - self.volumetricdata_imaginary = VolumetricData(structure, {"total": self.final_imaginary}) - self.volumetricdata_density = VolumetricData(structure, {"total": self.final_density}) - - def get_volumetricdata_real(self): - """ - Will return a VolumetricData object including the real part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_real"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_real - - def get_volumetricdata_imaginary(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_imaginary"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_imaginary - - def get_volumetricdata_density(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_density"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_density - - def write_file(self, filename="WAVECAR.vasp", part="real"): - """ - Will save the wavefunction in a file format that can be read by VESTA - This will only work if the wavefunction from lobster was constructed with: - "printLCAORealSpaceWavefunction kpoint 1 coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 2 3 4 - 5 6 " - or similar (the whole unit cell has to be covered!). - - Args: - filename: Filename for the output, e.g., WAVECAR.vasp - part: which part of the wavefunction will be saved ("real" or "imaginary") - """ - if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") - ): - self.set_volumetric_data(self.grid, self.structure) - if part == "real": - self.volumetricdata_real.write_file(filename) - elif part == "imaginary": - self.volumetricdata_imaginary.write_file(filename) - elif part == "density": - self.volumetricdata_density.write_file(filename) - else: - raise ValueError('part can be only "real" or "imaginary" or "density"') - - -# madleung and sitepotential classes -class MadelungEnergies: - """ - Class to read MadelungEnergies.lobster files generated by LOBSTER. - - Attributes: - madelungenergies_Mulliken (float): Float that gives the Madelung energy based on the Mulliken approach. - madelungenergies_Loewdin (float): Float that gives the Madelung energy based on the Loewdin approach. - ewald_splitting (float): Ewald splitting parameter to compute SitePotentials. - """ - - def __init__(self, filename: str = "MadelungEnergies.lobster"): - """ - Args: - filename: filename of the "MadelungEnergies.lobster" file. - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[5] - if len(data) == 0: - raise OSError("MadelungEnergies file contains no data.") - line = data.split() - self.ewald_splitting = float(line[0]) - self.madelungenergies_Mulliken = float(line[1]) - self.madelungenergies_Loewdin = float(line[2]) - - -class SitePotential: - """ - Class to read SitePotentials.lobster files generated by LOBSTER. - - Attributes: - atomlist (list[str]): List of atoms in SitePotentials.lobster. - types (list[str]): List of types of atoms in SitePotentials.lobster. - num_atoms (int): Number of atoms in SitePotentials.lobster. - sitepotentials_Mulliken (list[float]): List of Mulliken potentials of sites in SitePotentials.lobster. - sitepotentials_Loewdin (list[float]): List of Loewdin potentials of sites in SitePotentials.lobster. - madelung_Mulliken (float): Float that gives the Madelung energy based on the Mulliken approach. - madelung_Loewdin (float): Float that gives the Madelung energy based on the Loewdin approach. - ewald_splitting (float): Ewald Splitting parameter to compute SitePotentials. - """ - - def __init__(self, filename: str = "SitePotentials.lobster"): - """ - Args: - filename: filename for the SitePotentials file, typically "SitePotentials.lobster". - """ - # site_potentials - with zopen(filename, "rt") as f: - data = f.read().split("\n") - if len(data) == 0: - raise OSError("SitePotentials file contains no data.") - - self.ewald_splitting = float(data[0].split()[9]) - - data = data[5:-1] - self.num_atoms = len(data) - 2 - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.sitepotentials_Mulliken: list[float] = [] - self.sitepotentials_Loewdin: list[float] = [] - for atom in range(self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + str(line[0])) - self.types.append(line[1]) - self.sitepotentials_Mulliken.append(float(line[2])) - self.sitepotentials_Loewdin.append(float(line[3])) - - self.madelungenergies_Mulliken = float(data[self.num_atoms + 1].split()[3]) - self.madelungenergies_Loewdin = float(data[self.num_atoms + 1].split()[4]) - - def get_structure_with_site_potentials(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - - Args: - structure_filename: filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin charges as site properties. - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.sitepotentials_Mulliken - Loewdin = self.sitepotentials_Loewdin - site_properties = { - "Mulliken Site Potentials (eV)": Mulliken, - "Loewdin Site Potentials (eV)": Loewdin, - } - return struct.copy(site_properties=site_properties) - - -def get_orb_from_str(orbs): - """ - - Args: - orbs: list of two str, e.g. ["2p_x", "3s"]. - - Returns: - list of tw Orbital objects - - """ - # TODO: also useful for plotting of dos - orb_labs = [ - "s", - "p_y", - "p_z", - "p_x", - "d_xy", - "d_yz", - "d_z^2", - "d_xz", - "d_x^2-y^2", - "f_y(3x^2-y^2)", - "f_xyz", - "f_yz^2", - "f_z^3", - "f_xz^2", - "f_z(x^2-y^2)", - "f_x(x^2-3y^2)", - ] - orbitals = [(int(orb[0]), Orbital(orb_labs.index(orb[1:]))) for orb in orbs] - orb_label = f"{orbitals[0][0]}{orbitals[0][1].name}-{orbitals[1][0]}{orbitals[1][1].name}" # type: ignore - return orb_label, orbitals diff --git a/pymatgen/io/lobster/outputs.py.BASE.16529.py b/pymatgen/io/lobster/outputs.py.BASE.16529.py deleted file mode 100644 index 2eb02211079..00000000000 --- a/pymatgen/io/lobster/outputs.py.BASE.16529.py +++ /dev/null @@ -1,1748 +0,0 @@ -# Distributed under the terms of the MIT License - -""" -Module for reading Lobster output files. For more information -on LOBSTER see www.cohp.de. -""" - -from __future__ import annotations - -import collections -import fnmatch -import os -import re -import warnings -from collections import defaultdict -from typing import Any - -import numpy as np -from monty.io import zopen - -from pymatgen.core.structure import Structure -from pymatgen.electronic_structure.bandstructure import LobsterBandStructureSymmLine -from pymatgen.electronic_structure.core import Orbital, Spin -from pymatgen.electronic_structure.dos import Dos, LobsterCompleteDos -from pymatgen.io.vasp.inputs import Kpoints -from pymatgen.io.vasp.outputs import Vasprun, VolumetricData - -__author__ = "Janine George, Marco Esters" -__copyright__ = "Copyright 2017, The Materials Project" -__version__ = "0.2" -__maintainer__ = "Janine George, Marco Esters " -__email__ = "janine.george@uclouvain.be, esters@uoregon.edu" -__date__ = "Dec 13, 2017" - -MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - - -class Cohpcar: - """ - Class to read COHPCAR/COOPCAR files generated by LOBSTER. - - .. attribute: cohp_data - - Dict that contains the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have - a "length" key. - - .. attribute: efermi - - The Fermi energy in eV. - - .. attribute: energies - - Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - - .. attribute: is_spin_polarized - - Boolean to indicate if the calculation is spin polarized. - - .. attribute: orb_cohp - - orb_cohp[label] = {bond_data["orb_label"]: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}} - - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - are_cobis: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - - filename: Name of the COHPCAR file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "COOPCAR.lobster" - elif are_cobis: - filename = "COBICAR.lobster" - else: - filename = "COHPCAR.lobster" - - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - # The parameters line is the second line in a COHPCAR file. It - # contains all parameters that are needed to map the file. - parameters = contents[1].split() - # Subtract 1 to skip the average - num_bonds = int(parameters[0]) - 1 - self.efermi = float(parameters[-1]) - if int(parameters[1]) == 2: - spins = [Spin.up, Spin.down] - self.is_spin_polarized = True - else: - spins = [Spin.up] - self.is_spin_polarized = False - - # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] - cohp_data: dict[str, dict[str, Any]] = { - "average": { - "COHP": {spin: data[1 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - "ICOHP": {spin: data[2 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - } - } - - orb_cohp: dict[str, Any] = {} - # present for Lobster versions older than Lobster 2.2.0 - veryold = False - # the labeling had to be changed: there are more than one COHP for each atom combination - # this is done to make the labeling consistent with ICOHPLIST.lobster - bondnumber = 0 - for bond in range(num_bonds): - bond_data = self._get_bond_data(contents[3 + bond]) - - label = str(bondnumber) - - orbs = bond_data["orbitals"] - cohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 3] for s, spin in enumerate(spins)} - - icohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 4] for s, spin in enumerate(spins)} - if orbs is None: - bondnumber = bondnumber + 1 - label = str(bondnumber) - cohp_data[label] = { - "COHP": cohp, - "ICOHP": icohp, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - ) - else: - # present for Lobster versions older than Lobster 2.2.0 - if bondnumber == 0: - veryold = True - if veryold: - bondnumber += 1 - label = str(bondnumber) - - orb_cohp[label] = { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - - # present for lobster older than 2.2.0 - if veryold: - for bond_str in orb_cohp: - cohp_data[bond_str] = { - "COHP": None, - "ICOHP": None, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - self.orb_res_cohp = orb_cohp or None - self.cohp_data = cohp_data - - @staticmethod - def _get_bond_data(line: str) -> dict: - """ - Subroutine to extract bond label, site indices, and length from - a LOBSTER header line. The site indices are zero-based, so they - can be easily used with a Structure object. - - Example header line: No.4:Fe1->Fe9(2.4524893531900283) - Example header line for orbtial-resolved COHP: - No.1:Fe1[3p_x]->Fe2[3d_x^2-y^2](2.456180552772262) - - Args: - line: line in the COHPCAR header describing the bond. - - Returns: - Dict with the bond label, the bond length, a tuple of the site - indices, a tuple containing the orbitals (if orbital-resolved), - and a label for the orbitals (if orbital-resolved). - """ - - line_new = line.rsplit("(", 1) - length = float(line_new[-1][:-1]) - - sites = line_new[0].replace("->", ":").split(":")[1:3] - site_indices = tuple(int(re.split(r"\D+", site)[1]) - 1 for site in sites) - - if "[" in sites[0]: - orbs = [re.findall(r"\[(.*)\]", site)[0] for site in sites] - orb_label, orbitals = get_orb_from_str(orbs) - - else: - orbitals = None - orb_label = None - - bond_data = { - "length": length, - "sites": site_indices, - "orbitals": orbitals, - "orb_label": orb_label, - } - return bond_data - - -class Icohplist: - """ - Class to read ICOHPLIST/ICOOPLIST files generated by LOBSTER. - - .. attribute: are_coops - Boolean to indicate if the populations are COOPs or COHPs. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Icohplist - Dict containing the listfile data of the form: - {bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...}} - - .. attribute: IcohpCollection - IcohpCollection Object - - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of ICOOPs. - Defaults to False for ICOHPs. - are_cobis: Determines if the file is a list of ICOBIs. - Defaults to False for ICOHPs. - filename: Name of the ICOHPLIST file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "ICOOPLIST.lobster" - elif are_cobis: - filename = "ICOBILIST.lobster" - else: - filename = "ICOHPLIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("ICOHPLIST file contains no data.") - - # Which Lobster version? - if len(data[0].split()) == 8: - version = "3.1.1" - elif len(data[0].split()) == 6: - version = "2.2.1" - warnings.warn("Please consider using the new Lobster version. See www.cohp.de.") - else: - raise ValueError - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - if "distance" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = True - else: - self.is_spin_polarized = False - - # check if orbitalwise ICOHPLIST - # include case when there is only one ICOHP!!! - if len(data) > 2 and "_" in data[1].split()[1]: - self.orbitalwise = True - else: - self.orbitalwise = False - - if self.orbitalwise: - data_without_orbitals = [] - data_orbitals = [] - for line in data: - if "_" not in line.split()[1]: - data_without_orbitals.append(line) - else: - data_orbitals.append(line) - - else: - data_without_orbitals = data - - if "distance" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("ICOHPLIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - list_labels = [] - list_atom1 = [] - list_atom2 = [] - list_length = [] - list_translation = [] - list_num = [] - list_icohp = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - icohp = {} - if version == "2.2.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - icohp[Spin.up] = float(line[4]) - num = int(line[5]) - translation = [0, 0, 0] - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[4]) - - elif version == "3.1.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - translation = [int(line[4]), int(line[5]), int(line[6])] - icohp[Spin.up] = float(line[7]) - num = int(1) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) - - list_labels.append(label) - list_atom1.append(atom1) - list_atom2.append(atom2) - list_length.append(length) - list_translation.append(translation) - list_num.append(num) - list_icohp.append(icohp) - - list_orb_icohp: list[dict] | None = None - if self.orbitalwise: - list_orb_icohp = [] - num_orbs = len(data_orbitals) // 2 if self.is_spin_polarized else len(data_orbitals) - - for i_data_orb in range(num_orbs): - data_orb = data_orbitals[i_data_orb] - icohp = {} - line = data_orb.split() - label = f"{line[0]}" - orbs = re.findall(r"_(.*?)(?=\s)", data_orb) - orb_label, orbitals = get_orb_from_str(orbs) - icohp[Spin.up] = float(line[7]) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_orbitals[num_orbs + i_data_orb].split()[7]) - - if len(list_orb_icohp) < int(label): - list_orb_icohp.append({orb_label: {"icohp": icohp, "orbitals": orbitals}}) - else: - list_orb_icohp[int(label) - 1][orb_label] = {"icohp": icohp, "orbitals": orbitals} - - # to avoid circular dependencies - from pymatgen.electronic_structure.cohp import IcohpCollection - - self._icohpcollection = IcohpCollection( - are_coops=are_coops, - are_cobis=are_cobis, - list_labels=list_labels, - list_atom1=list_atom1, - list_atom2=list_atom2, - list_length=list_length, - list_translation=list_translation, - list_num=list_num, - list_icohp=list_icohp, - is_spin_polarized=self.is_spin_polarized, - list_orb_icohp=list_orb_icohp, - ) - - @property - def icohplist(self) -> dict[Any, dict[str, Any]]: - """ - Returns: icohplist compatible with older version of this class - """ - icohplist_new = {} - for key, value in self._icohpcollection._icohplist.items(): - icohplist_new[key] = { - "length": value._length, - "number_of_bonds": value._num, - "icohp": value._icohp, - "translation": value._translation, - "orbitals": value._orbitals, - } - return icohplist_new - - @property - def icohpcollection(self): - """ - Returns: IcohpCollection object - """ - return self._icohpcollection - - -class Doscar: - """ - Class to deal with Lobster's projected DOS and local projected DOS. - The beforehand quantum-chemical calculation was performed with VASP - - .. attribute:: completedos - - LobsterCompleteDos Object - - .. attribute:: pdos - List of Dict including numpy arrays with pdos. Access as pdos[atomindex]['orbitalstring']['Spin.up/Spin.down'] - - .. attribute:: tdos - Dos Object of the total density of states - - .. attribute:: energies - numpy array of the energies at which the DOS was calculated (in eV, relative to Efermi) - - .. attribute:: tdensities - tdensities[Spin.up]: numpy array of the total density of states for the Spin.up contribution at each of the - energies - tdensities[Spin.down]: numpy array of the total density of states for the Spin.down contribution at each of the - energies - - if is_spin_polarized=False: - tdensities[Spin.up]: numpy array of the total density of states - - - .. attribute:: itdensities: - itdensities[Spin.up]: numpy array of the total density of states for the Spin.up contribution at each of the - energies - itdensities[Spin.down]: numpy array of the total density of states for the Spin.down contribution at each of the - energies - - if is_spin_polarized=False: - itdensities[Spin.up]: numpy array of the total density of states - - - .. attribute:: is_spin_polarized - Boolean. Tells if the system is spin polarized - """ - - def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str = "POSCAR", - dftprogram: str = "Vasp", - ): - """ - Args: - doscar: DOSCAR filename, typically "DOSCAR.lobster" - structure_file: for vasp, this is typically "POSCAR" - dftprogram: so far only "vasp" is implemented - """ - self._doscar = doscar - if dftprogram == "Vasp": - self._final_structure = Structure.from_file(structure_file) - - self._parse_doscar() - - def _parse_doscar(self): - doscar = self._doscar - - tdensities = {} - itdensities = {} - with zopen(doscar, "rt") as f: - natoms = int(f.readline().split()[0]) - efermi = float([f.readline() for nn in range(4)][3].split()[17]) - dos = [] - orbitals = [] - for _atom in range(natoms + 1): - line = f.readline() - ndos = int(line.split()[2]) - orbitals.append(line.split(";")[-1].split()) - line = f.readline().split() - cdos = np.zeros((ndos, len(line))) - cdos[0] = np.array(line) - for nd in range(1, ndos): - line = f.readline().split() - cdos[nd] = np.array(line) - dos.append(cdos) - doshere = np.array(dos[0]) - if len(doshere[0, :]) == 5: - self._is_spin_polarized = True - elif len(doshere[0, :]) == 3: - self._is_spin_polarized = False - else: - raise ValueError("There is something wrong with the DOSCAR. Can't extract spin polarization.") - energies = doshere[:, 0] - if not self._is_spin_polarized: - tdensities[Spin.up] = doshere[:, 1] - itdensities[Spin.up] = doshere[:, 2] - pdoss = [] - spin = Spin.up - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - orbnumber = orbnumber + 1 - pdoss.append(pdos) - else: - tdensities[Spin.up] = doshere[:, 1] - tdensities[Spin.down] = doshere[:, 2] - itdensities[Spin.up] = doshere[:, 3] - itdensities[Spin.down] = doshere[:, 4] - pdoss = [] - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - spin = Spin.down if j % 2 == 0 else Spin.up - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - if j % 2 == 0: - orbnumber = orbnumber + 1 - pdoss.append(pdos) - - self._efermi = efermi - self._pdos = pdoss - self._tdos = Dos(efermi, energies, tdensities) - self._energies = energies - self._tdensities = tdensities - self._itdensities = itdensities - final_struct = self._final_structure - - pdossneu = {final_struct[i]: pdos for i, pdos in enumerate(self._pdos)} - - self._completedos = LobsterCompleteDos(final_struct, self._tdos, pdossneu) - - @property - def completedos(self) -> LobsterCompleteDos: - """ - :return: CompleteDos - """ - return self._completedos - - @property - def pdos(self) -> list: - """ - :return: Projected DOS - """ - return self._pdos - - @property - def tdos(self) -> Dos: - """ - :return: Total DOS - """ - return self._tdos - - @property - def energies(self) -> np.ndarray: - """ - :return: Energies - """ - return self._energies - - @property - def tdensities(self) -> np.ndarray: - """ - :return: total densities as a np.ndarray - """ - return self._tdensities - - @property - def itdensities(self) -> np.ndarray: - """ - :return: integrated total densities as a np.ndarray - """ - return self._itdensities - - @property - def is_spin_polarized(self) -> bool: - """ - :return: Whether run is spin polarized. - """ - return self._is_spin_polarized - - -class Charge: - """ - Class to read CHARGE files generated by LOBSTER - - .. attribute: atomlist - List of atoms in CHARGE.lobster - .. attribute: types - List of types of atoms in CHARGE.lobster - .. attribute: Mulliken - List of Mulliken charges of atoms in CHARGE.lobster - .. attribute: Loewdin - List of Loewdin charges of atoms in CHARGE.Loewdin - .. attribute: num_atoms - Number of atoms in CHARGE.lobster - - """ - - def __init__(self, filename: str = "CHARGE.lobster"): - """ - Args: - filename: filename for the CHARGE file, typically "CHARGE.lobster" - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[3:-3] - if len(data) == 0: - raise OSError("CHARGES file contains no data.") - - self.num_atoms = len(data) - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.Mulliken: list[float] = [] - self.Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + line[0]) - self.types.append(line[1]) - self.Mulliken.append(float(line[2])) - self.Loewdin.append(float(line[3])) - - def get_structure_with_charges(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - Args: - structure_filename: filename of POSCAR - Returns: - Structure Object with Mulliken and Loewdin charges as site properties - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.Mulliken - Loewdin = self.Loewdin - site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - new_struct = struct.copy(site_properties=site_properties) - return new_struct - - -class Lobsterout: - """ - Class to read in the lobsterout and evaluate the spilling, save the basis, save warnings, save infos - - .. attribute: basis_functions - list of basis functions that were used in lobster run as strings - - .. attribute: basis_type - list of basis type that were used in lobster run as strings - - .. attribute: charge_spilling - list of charge spilling (first entry: result for spin 1, second entry: result for spin 2 or not present) - - .. attribute: dft_program - string representing the dft program used for the calculation of the wave function - - .. attribute: elements - list of strings of elements that were present in lobster calculation - - .. attribute: has_charge - Boolean, indicates that CHARGE.lobster is present - - .. attribute: has_cohpcar - Boolean, indicates that COHPCAR.lobster and ICOHPLIST.lobster are present - - .. attribute: has_madelung - Boolean, indicates that SitePotentials.lobster and MadelungEnergies.lobster are present - - .. attribute: has_coopcar - Boolean, indicates that COOPCAR.lobster and ICOOPLIST.lobster are present - - .. attribute: has_cobicar - Boolean, indicates that COBICAR.lobster and ICOBILIST.lobster are present - - .. attribute: has_doscar - Boolean, indicates that DOSCAR.lobster is present - - .. attribute: has_doscar_lso - Boolean, indicates that DOSCAR.LSO.lobster is present - - .. attribute: has_projection - Boolean, indicates that projectionData.lobster is present - - .. attribute: has_bandoverlaps - Boolean, indicates that bandOverlaps.lobster is present - - .. attribute: has_density_of_energies - Boolean, indicates that DensityOfEnergy.lobster is present - - .. attribute: has_fatbands - Boolean, indicates that fatband calculation was performed - - .. attribute: has_grosspopulation - Boolean, indicates that GROSSPOP.lobster is present - - .. attribute: info_lines - string with additional infos on the run - - .. attribute: info_orthonormalization - string with infos on orthonormalization - - .. attribute: is_restart_from_projection - Boolean that indicates that calculation was restartet from existing projection file - - .. attribute: lobster_version - string that indicates Lobster version - - .. attribute: number_of_spins - Integer indicating the number of spins - - .. attribute: number_of_threads - integer that indicates how many threads were used - - .. attribute: timing - dict with infos on timing - - .. attribute: total_spilling - list of values indicating the total spilling for spin channel 1 (and spin channel 2) - - .. attribute: warning_lines - string with all warnings - - """ - - # TODO: add tests for skipping COBI and madelung - # TODO: add tests for including COBI and madelung - def __init__(self, filename="lobsterout"): - """ - Args: - filename: filename of lobsterout - """ - # read in file - with zopen(filename, "rt") as f: - data = f.read().split("\n") # [3:-3] - if len(data) == 0: - raise OSError("lobsterout does not contain any data") - - # check if Lobster starts from a projection - self.is_restart_from_projection = "loading projection from projectionData.lobster..." in data - - self.lobster_version = self._get_lobster_version(data=data) - - self.number_of_threads = int(self._get_threads(data=data)) - self.dft_program = self._get_dft_program(data=data) - - self.number_of_spins = self._get_number_of_spins(data=data) - chargespilling, totalspilling = self._get_spillings(data=data, number_of_spins=self.number_of_spins) - self.charge_spilling = chargespilling - self.total_spilling = totalspilling - - ( - elements, - basistype, - basisfunctions, - ) = self._get_elements_basistype_basisfunctions(data=data) - self.elements = elements - self.basis_type = basistype - self.basis_functions = basisfunctions - - wall_time, user_time, sys_time = self._get_timing(data=data) - timing = {} - timing["wall_time"] = wall_time - timing["user_time"] = user_time - timing["sys_time"] = sys_time - self.timing = timing - - warninglines = self._get_all_warning_lines(data=data) - self.warning_lines = warninglines - - orthowarning = self._get_warning_orthonormalization(data=data) - self.info_orthonormalization = orthowarning - - infos = self._get_all_info_lines(data=data) - self.info_lines = infos - - self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data - self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data - ) - self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data - ) - self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data - ) - self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data - ) - - self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data - self.has_projection = "saving projection to projectionData.lobster..." in data - self.has_bandoverlaps = "WARNING: I dumped the band overlap matrices to the file bandOverlaps.lobster." in data - self.has_fatbands = self._has_fatband(data=data) - self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data - self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data - self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data - ) - - def get_doc(self): - """ - Returns: LobsterDict with all the information stored in lobsterout - """ - LobsterDict = {} - # check if Lobster starts from a projection - LobsterDict["restart_from_projection"] = self.is_restart_from_projection - LobsterDict["lobster_version"] = self.lobster_version - LobsterDict["threads"] = self.number_of_threads - LobsterDict["dft_program"] = self.dft_program - - LobsterDict["charge_spilling"] = self.charge_spilling - LobsterDict["total_spilling"] = self.total_spilling - - LobsterDict["elements"] = self.elements - LobsterDict["basis_type"] = self.basis_type - LobsterDict["basis_functions"] = self.basis_functions - - LobsterDict["timing"] = self.timing - - LobsterDict["warning_lines"] = self.warning_lines - - LobsterDict["info_orthonormalization"] = self.info_orthonormalization - - LobsterDict["info_lines"] = self.info_lines - - LobsterDict["has_doscar"] = self.has_doscar - LobsterDict["has_doscar_lso"] = self.has_doscar_lso - LobsterDict["has_cohpcar"] = self.has_cohpcar - LobsterDict["has_coopcar"] = self.has_coopcar - LobsterDict["has_cobicar"] = self.has_cobicar - LobsterDict["has_charge"] = self.has_charge - LobsterDict["has_madelung"] = self.has_madelung - LobsterDict["has_projection"] = self.has_projection - LobsterDict["has_bandoverlaps"] = self.has_bandoverlaps - LobsterDict["has_fatbands"] = self.has_fatbands - LobsterDict["has_grosspopulation"] = self.has_grosspopulation - LobsterDict["has_density_of_energies"] = self.has_density_of_energies - - return LobsterDict - - @staticmethod - def _get_lobster_version(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[0] == "LOBSTER": - return splitrow[1] - raise RuntimeError("Version not found.") - - @staticmethod - def _has_fatband(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[1] == "FatBand": - return True - return False - - @staticmethod - def _get_dft_program(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 4 and splitrow[3] == "program...": - return splitrow[4] - return None - - @staticmethod - def _get_number_of_spins(data): - if "spillings for spin channel 2" in data: - return 2 - return 1 - - @staticmethod - def _get_threads(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): - return splitrow[10] - raise ValueError("Threads not found.") - - @staticmethod - def _get_spillings(data, number_of_spins): - charge_spilling = [] - total_spilling = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 2 and splitrow[2] == "spilling:": - if splitrow[1] == "charge": - charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - if splitrow[1] == "total": - total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - - if len(charge_spilling) == number_of_spins and len(total_spilling) == number_of_spins: - break - - return charge_spilling, total_spilling - - @staticmethod - def _get_elements_basistype_basisfunctions(data): - begin = False - end = False - elements = [] - basistype = [] - basisfunctions = [] - for row in data: - if begin and not end: - splitrow = row.split() - if splitrow[0] not in [ - "INFO:", - "WARNING:", - "setting", - "calculating", - "post-processing", - "saving", - "spillings", - "writing", - ]: - elements.append(splitrow[0]) - basistype.append(splitrow[1].replace("(", "").replace(")", "")) - # last sign is a '' - basisfunctions.append(splitrow[2:]) - else: - end = True - if "setting up local basis functions..." in row: - begin = True - return elements, basistype, basisfunctions - - @staticmethod - def _get_timing(data): - # will give back wall, user and sys time - begin = False - # end=False - # time=[] - - for row in data: - splitrow = row.split() - if "finished" in splitrow: - begin = True - if begin: - if "wall" in splitrow: - wall_time = splitrow[2:10] - if "user" in splitrow: - user_time = splitrow[0:8] - if "sys" in splitrow: - sys_time = splitrow[0:8] - - wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} - user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} - sys_time_dict = {"h": sys_time[0], "min": sys_time[2], "s": sys_time[4], "ms": sys_time[6]} - - return wall_time_dict, user_time_dict, sys_time_dict - - @staticmethod - def _get_warning_orthonormalization(data): - orthowarning = [] - for row in data: - splitrow = row.split() - if "orthonormalized" in splitrow: - orthowarning.append(" ".join(splitrow[1:])) - return orthowarning - - @staticmethod - def _get_all_warning_lines(data): - ws = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "WARNING:": - ws.append(" ".join(splitrow[1:])) - return ws - - @staticmethod - def _get_all_info_lines(data): - infos = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "INFO:": - infos.append(" ".join(splitrow[1:])) - return infos - - -class Fatband: - """ - Reads in FATBAND_x_y.lobster files - - .. attribute: efermi - - efermi that was read in from vasprun.xml - - .. attribute: eigenvals - {Spin.up:[][],Spin.down:[][]}, the first index of the array - [][] refers to the band and the second to the index of the - kpoint. The kpoints are ordered according to the order of the - kpoints array. If the band structure is not spin polarized, we - only store one data set under Spin.up. - - .. attribute: is_spinpolarized - - Boolean that tells you whether this was a spin-polarized calculation - - .. attribute: kpoints_array - - list of kpoint as numpy arrays, in frac_coords of the given lattice by default - - .. attribute: label_dict - - (dict) of {} this link a kpoint (in frac coords or Cartesian coordinates depending on the coords). - - .. attribute: lattice - - lattice object of reciprocal lattice as read in from vasprun.xml - - .. attribute: nbands - - number of bands used in the calculation - - .. attribute: p_eigenvals - - dict of orbital projections as {spin: array of dict}. - The indices of the array are [band_index, kpoint_index]. - The dict is then built the following way: - {"string of element": "string of orbital as read in from FATBAND file"} - If the band structure is not spin polarized, we only store one data set under Spin.up. - - .. attribute: structure - - structure read in from vasprun.xml - """ - - def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): - """ - Args: - filenames (list or string): can be a list of file names or a path to a folder folder from which all - "FATBAND_*" files will be read - vasprun: corresponding vasprun file - Kpointsfile: KPOINTS file for bandstructure calculation, typically "KPOINTS" - """ - warnings.warn("Make sure all relevant FATBAND files were generated and read in!") - warnings.warn("Use Lobster 3.2.0 or newer for fatband calculations!") - - VASPRUN = Vasprun( - filename=vasprun, - ionic_step_skip=None, - ionic_step_offset=0, - parse_dos=True, - parse_eigen=False, - parse_projected_eigen=False, - parse_potcar_file=False, - occu_tol=1e-8, - exception_on_bad_xml=True, - ) - self.structure = VASPRUN.final_structure - self.lattice = self.structure.lattice.reciprocal_lattice - self.efermi = VASPRUN.efermi - kpoints_object = Kpoints.from_file(Kpointsfile) - - atomtype = [] - atomnames = [] - orbital_names = [] - - if not isinstance(filenames, list) or filenames is None: - filenames_new = [] - if filenames is None: - filenames = "." - for file in os.listdir(filenames): - if fnmatch.fnmatch(file, "FATBAND_*.lobster"): - filenames_new.append(os.path.join(filenames, file)) - filenames = filenames_new - if len(filenames) == 0: - raise ValueError("No FATBAND files in folder or given") - for filename in filenames: - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - atomnames.append(os.path.split(filename)[1].split("_")[1].capitalize()) - parameters = contents[0].split() - atomtype.append(re.split(r"[0-9]+", parameters[3])[0].capitalize()) - orbital_names.append(parameters[4]) - - # get atomtype orbital dict - atom_orbital_dict = {} - for iatom, atom in enumerate(atomnames): - if atom not in atom_orbital_dict: - atom_orbital_dict[atom] = [] - atom_orbital_dict[atom].append(orbital_names[iatom]) - # test if there are the same orbitals twice or if two different formats were used or if all necessary orbitals - # are there - for items in atom_orbital_dict.values(): - if len(set(items)) != len(items): - raise ValueError("The are two FATBAND files for the same atom and orbital. The program will stop.") - split = [] - for item in items: - split.append(item.split("_")[0]) - for number in collections.Counter(split).values(): - if number not in (1, 3, 5, 7): - raise ValueError( - "Make sure all relevant orbitals were generated and that no duplicates (2p and 2p_x) are " - "present" - ) - - kpoints_array = [] - for ifilename, filename in enumerate(filenames): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - if ifilename == 0: - self.nbands = int(parameters[6]) - self.number_kpts = kpoints_object.num_kpts - int(contents[1].split()[2]) + 1 - - if len(contents[1:]) == self.nbands + 2: - self.is_spinpolarized = False - elif len(contents[1:]) == self.nbands * 2 + 2: - self.is_spinpolarized = True - else: - linenumbers = [] - for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): - if line.split()[0] == "#": - linenumbers.append(iline) - - if ifilename == 0: - if len(linenumbers) == 2: - self.is_spinpolarized = True - else: - self.is_spinpolarized = False - - if ifilename == 0: - eigenvals = {} - eigenvals[Spin.up] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - if self.is_spinpolarized: - eigenvals[Spin.down] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - - p_eigenvals = {} - p_eigenvals[Spin.up] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - if self.is_spinpolarized: - p_eigenvals[Spin.down] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - ikpoint = -1 - for line in contents[1:-1]: - if line.split()[0] == "#": - KPOINT = np.array( - [ - float(line.split()[4]), - float(line.split()[5]), - float(line.split()[6]), - ] - ) - if ifilename == 0: - kpoints_array.append(KPOINT) - - linenumber = 0 - iband = 0 - ikpoint += 1 - if linenumber == self.nbands: - iband = 0 - if line.split()[0] != "#": - if linenumber < self.nbands: - if ifilename == 0: - eigenvals[Spin.up][iband][ikpoint] = float(line.split()[1]) + self.efermi - - p_eigenvals[Spin.up][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - if linenumber >= self.nbands and self.is_spinpolarized: - if ifilename == 0: - eigenvals[Spin.down][iband][ikpoint] = float(line.split()[1]) + self.efermi - p_eigenvals[Spin.down][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - - linenumber += 1 - iband += 1 - - self.kpoints_array = kpoints_array - self.eigenvals = eigenvals - self.p_eigenvals = p_eigenvals - - label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): - if label is not None: - label_dict[label] = kpoints_array[ilabel] - - self.label_dict = label_dict - - def get_bandstructure(self): - """ - Returns a LobsterBandStructureSymmLine object which can be plotted with a normal BSPlotter - """ - return LobsterBandStructureSymmLine( - kpoints=self.kpoints_array, - eigenvals=self.eigenvals, - lattice=self.lattice, - efermi=self.efermi, - labels_dict=self.label_dict, - structure=self.structure, - projections=self.p_eigenvals, - ) - - -class Bandoverlaps: - """ - Class to read in bandOverlaps.lobster files. These files are not created during every Lobster run. - .. attribute: bandoverlapsdict is a dict of the following form: - {spin:{"kpoint as string": {"maxDeviation": float that describes the max deviation, "matrix": 2D - array of the size number of bands times number of bands including the overlap matrices with } }} - - .. attribute: maxDeviation is a list of floats describing the maximal Deviation for each problematic kpoint - - """ - - def __init__(self, filename: str = "bandOverlaps.lobster"): - """ - Args: - filename: filename of the "bandOverlaps.lobster" file - """ - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - spin_numbers = [0, 1] if contents[0].split()[-1] == "0" else [1, 2] - - self._read(contents, spin_numbers) - - def _read(self, contents: list, spin_numbers: list): - """ - Will read in all contents of the file - Args: - contents: list of strings - spin_numbers: list of spin numbers depending on `Lobster` version. - """ - self.bandoverlapsdict: dict[Any, dict] = {} # Any is spin number 1 or -1 - self.max_deviation = [] - # This has to be done like this because there can be different numbers of problematic k-points per spin - for line in contents: - if f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[0]}" in line: - spin = Spin.up - elif f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[1]}" in line: - spin = Spin.down - elif "k-point" in line: - kpoint = line.split(" ") - kpoint_array = [] - for kpointel in kpoint: - if kpointel not in ["at", "k-point", ""]: - kpoint_array.append(str(kpointel)) - - elif "maxDeviation" in line: - if spin not in self.bandoverlapsdict: - self.bandoverlapsdict[spin] = {} - if " ".join(kpoint_array) not in self.bandoverlapsdict[spin]: - self.bandoverlapsdict[spin][" ".join(kpoint_array)] = {} - maxdev = line.split(" ")[2] - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["maxDeviation"] = float(maxdev) - self.max_deviation.append(float(maxdev)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"] = [] - - else: - overlaps = [] - for el in line.split(" "): - if el not in [""]: - overlaps.append(float(el)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"].append(overlaps) - - def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool: - """ - Will check if the maxDeviation from the ideal bandoverlap is smaller or equal to limit_maxDeviation - Args: - limit_maxDeviation: limit of the maxDeviation - Returns: - Boolean that will give you information about the quality of the projection - """ - return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) - - def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, - ) -> bool: - """ - Will check if the deviation from the ideal bandoverlap of all occupied bands is smaller or equal to - limit_deviation - - Args: - number_occ_bands_spin_up (int): number of occupied bands of spin up - number_occ_bands_spin_down (int): number of occupied bands of spin down - spin_polarized (bool): If True, then it was a spin polarized calculation - limit_deviation (float): limit of the maxDeviation - Returns: - Boolean that will give you information about the quality of the projection - """ - for matrix in self.bandoverlapsdict[Spin.up].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if iband1 < number_occ_bands_spin_up and iband2 < number_occ_bands_spin_up: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - - if spin_polarized: - for matrix in self.bandoverlapsdict[Spin.down].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if number_occ_bands_spin_down is not None: - if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - else: - ValueError("number_occ_bands_spin_down has to be specified") - return True - - -class Grosspop: - """ - Class to read in GROSSPOP.lobster files. - - .. attribute: list_dict_grosspop - which is a list of dicts including all information about the grosspopulations, one sample dict looks like this: - {'element': 'O', 'Mulliken GP': {'2s': '1.80', '2p_y': '1.83', '2p_z': '1.79', '2p_x': '1.75', 'total': '7.18'}, - 'Loewdin GP': {'2s': '1.60', '2p_y': '1.82', '2p_z': '1.77', '2p_x': '1.73', 'total': '6.92'}} - The 0. entry of the list refers to the first atom in GROSSPOP.lobster and so on. - """ - - def __init__(self, filename: str = "GROSSPOP.lobster"): - """ - Args: - filename: filename of the "GROSSPOP.lobster" file - """ - # opens file - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - self.list_dict_grosspop = [] - # transfers content of file to list of dict - for line in contents[3:]: - cleanline = [i for i in line.split(" ") if i != ""] - if len(cleanline) == 5: - smalldict = {} - smalldict["element"] = cleanline[1] - smalldict["Mulliken GP"] = {} - smalldict["Loewdin GP"] = {} - smalldict["Mulliken GP"][cleanline[2]] = float(cleanline[3]) - smalldict["Loewdin GP"][cleanline[2]] = float(cleanline[4]) - elif len(cleanline) > 0: - smalldict["Mulliken GP"][cleanline[0]] = float(cleanline[1]) - smalldict["Loewdin GP"][cleanline[0]] = float(cleanline[2]) - if "total" in cleanline[0]: - self.list_dict_grosspop.append(smalldict) - - def get_structure_with_total_grosspop(self, structure_filename: str) -> Structure: - """ - Get a Structure with Mulliken and Loewdin total grosspopulations as site properties - Args: - structure_filename (str): filename of POSCAR - Returns: - Structure Object with Mulliken and Loewdin total grosspopulations as site properties - """ - struct = Structure.from_file(structure_filename) - site_properties: dict[str, Any] = {} - mullikengp = [] - loewdingp = [] - for grosspop in self.list_dict_grosspop: - mullikengp.append(grosspop["Mulliken GP"]["total"]) - loewdingp.append(grosspop["Loewdin GP"]["total"]) - - site_properties = { - "Total Mulliken GP": mullikengp, - "Total Loewdin GP": loewdingp, - } - new_struct = struct.copy(site_properties=site_properties) - return new_struct - - -class Wavefunction: - """ - Class to read in wave function files from Lobster and transfer them into an object of the type VolumetricData - - .. attribute: grid - - grid for the wave function [Nx+1,Ny+1,Nz+1] - - .. attribute: points - - list of points - - .. attribute: real - - list of real part of wave function - - .. attribute: imaginary - - list of imaginary part of wave function - - .. attribute: distance - - list of distance to first point in wave function file - """ - - def __init__(self, filename, structure): - """ - Args: - filename: filename of wavecar file from Lobster - structure: Structure object (e.g., created by Structure.from_file("")) - """ - self.filename = filename - self.structure = structure - - ( - self.grid, - self.points, - self.real, - self.imaginary, - self.distance, - ) = Wavefunction._parse_file(filename) - - @staticmethod - def _parse_file(filename): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - points = [] - distance = [] - real = [] - imaginary = [] - splitline = contents[0].split() - grid = [int(splitline[7]), int(splitline[8]), int(splitline[9])] - for line in contents[1:]: - splitline = line.split() - if len(splitline) >= 6: - points.append([float(splitline[0]), float(splitline[1]), float(splitline[2])]) - distance.append(float(splitline[3])) - real.append(float(splitline[4])) - imaginary.append(float(splitline[5])) - - if not len(real) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - if not len(imaginary) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - return grid, points, real, imaginary, distance - - def set_volumetric_data(self, grid, structure): - """ - Will create the VolumetricData Objects - - Args: - grid: grid on which wavefunction was calculated, e.g. [1,2,2] - structure: Structure object - """ - Nx = grid[0] - 1 - Ny = grid[1] - 1 - Nz = grid[2] - 1 - a = structure.lattice.matrix[0] - b = structure.lattice.matrix[1] - c = structure.lattice.matrix[2] - new_x = [] - new_y = [] - new_z = [] - new_real = [] - new_imaginary = [] - new_density = [] - - runner = 0 - for x in range(0, Nx + 1): - for y in range(0, Ny + 1): - for z in range(0, Nz + 1): - x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] - y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] - z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - - if x != Nx and y != Ny and z != Nz: - if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) - ): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x.append(x_here) - new_y.append(y_here) - new_z.append(z_here) - - new_real.append(self.real[runner]) - new_imaginary.append(self.imaginary[runner]) - new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) - - runner += 1 - - self.final_real = np.reshape(new_real, [Nx, Ny, Nz]) - self.final_imaginary = np.reshape(new_imaginary, [Nx, Ny, Nz]) - self.final_density = np.reshape(new_density, [Nx, Ny, Nz]) - - self.volumetricdata_real = VolumetricData(structure, {"total": self.final_real}) - self.volumetricdata_imaginary = VolumetricData(structure, {"total": self.final_imaginary}) - self.volumetricdata_density = VolumetricData(structure, {"total": self.final_density}) - - def get_volumetricdata_real(self): - """ - Will return a VolumetricData object including the real part of the wave function - - Returns: VolumetricData object - """ - if not hasattr(self, "volumetricdata_real"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_real - - def get_volumetricdata_imaginary(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function - - Returns: VolumetricData object - """ - if not hasattr(self, "volumetricdata_imaginary"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_imaginary - - def get_volumetricdata_density(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function - - Returns: VolumetricData object - """ - if not hasattr(self, "volumetricdata_density"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_density - - def write_file(self, filename="WAVECAR.vasp", part="real"): - """ - Will save the wavefunction in a file format that can be read by VESTA - This will only work if the wavefunction from lobster was constructed with: - "printLCAORealSpaceWavefunction kpoint 1 coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 2 3 4 - 5 6 " - or similar (the whole unit cell has to be covered!) - - Args: - filename: Filename for the output, e.g., WAVECAR.vasp - part: which part of the wavefunction will be saved ("real" or "imaginary") - """ - if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") - ): - self.set_volumetric_data(self.grid, self.structure) - if part == "real": - self.volumetricdata_real.write_file(filename) - elif part == "imaginary": - self.volumetricdata_imaginary.write_file(filename) - elif part == "density": - self.volumetricdata_density.write_file(filename) - else: - raise ValueError('part can be only "real" or "imaginary" or "density"') - - -# madleung and sitepotential classes -class MadelungEnergies: - """ - Class to read MadelungEnergies.lobster files generated by LOBSTER - - .. attribute: madelungenergies_Mulliken - float that gives the madelung energy based on the Mulliken approach - .. attribute: madelungenergies_Loewdin - float that gives the madelung energy based on the Loewdin approach - .. attribute: ewald_splitting - Ewald Splitting parameter to compute SitePotentials - - """ - - def __init__(self, filename: str = "MadelungEnergies.lobster"): - """ - Args: - filename: filename of the "MadelungEnergies.lobster" file - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[5] - if len(data) == 0: - raise OSError("MadelungEnergies file contains no data.") - line = data.split() - self.ewald_splitting = float(line[0]) - self.madelungenergies_Mulliken = float(line[1]) - self.madelungenergies_Loewdin = float(line[2]) - - -class SitePotential: - """ - Class to read SitePotentials.lobster files generated by LOBSTER - - .. attribute: atomlist - List of atoms in SitePotentials.lobster - .. attribute: types - List of types of atoms in SitePotentials.lobster - .. attribute: num_atoms - Number of atoms in SitePotentials.lobster - .. attribute: sitepotentials_Mulliken - List of Mulliken potentials of sites in SitePotentials.lobster - .. attribute: sitepotentials_Loewdin - List of Loewdin potentials of sites in SitePotentials.lobster - .. attribute: madelung_Mulliken - float that gives the madelung energy based on the Mulliken approach - .. attribute: madelung_Loewdin - float that gives the madelung energy based on the Loewdin approach - .. attribute: ewald_splitting - Ewald Splitting parameter to compute SitePotentials - """ - - def __init__(self, filename: str = "SitePotentials.lobster"): - """ - Args: - filename: filename for the SitePotentials file, typically "SitePotentials.lobster" - """ - # site_potentials - with zopen(filename, "rt") as f: - data = f.read().split("\n") - if len(data) == 0: - raise OSError("SitePotentials file contains no data.") - - self.ewald_splitting = float(data[0].split()[9]) - - data = data[5:-1] - self.num_atoms = len(data) - 2 - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.sitepotentials_Mulliken: list[float] = [] - self.sitepotentials_Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + str(line[0])) - self.types.append(line[1]) - self.sitepotentials_Mulliken.append(float(line[2])) - self.sitepotentials_Loewdin.append(float(line[3])) - - self.madelungenergies_Mulliken = float(data[self.num_atoms + 1].split()[3]) - self.madelungenergies_Loewdin = float(data[self.num_atoms + 1].split()[4]) - - def get_structure_with_site_potentials(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - Args: - structure_filename: filename of POSCAR - Returns: - Structure Object with Mulliken and Loewdin charges as site properties - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.sitepotentials_Mulliken - Loewdin = self.sitepotentials_Loewdin - site_properties = { - "Mulliken Site Potentials (eV)": Mulliken, - "Loewdin Site Potentials (eV)": Loewdin, - } - new_struct = struct.copy(site_properties=site_properties) - return new_struct - - -def get_orb_from_str(orbs): - """ - - Args: - orbs: list of two str, e.g. ["2p_x", "3s"] - - Returns: - list of tw Orbital objects - - """ - # TODO: also useful for plotting of dos - orb_labs = [ - "s", - "p_y", - "p_z", - "p_x", - "d_xy", - "d_yz", - "d_z^2", - "d_xz", - "d_x^2-y^2", - "f_y(3x^2-y^2)", - "f_xyz", - "f_yz^2", - "f_z^3", - "f_xz^2", - "f_z(x^2-y^2)", - "f_x(x^2-3y^2)", - ] - orbitals = [(int(orb[0]), Orbital(orb_labs.index(orb[1:]))) for orb in orbs] - orb_label = f"{orbitals[0][0]}{orbitals[0][1].name}-{orbitals[1][0]}{orbitals[1][1].name}" # type: ignore - return orb_label, orbitals diff --git a/pymatgen/io/lobster/outputs.py.LOCAL.16529.py b/pymatgen/io/lobster/outputs.py.LOCAL.16529.py deleted file mode 100644 index 2ae04213f28..00000000000 --- a/pymatgen/io/lobster/outputs.py.LOCAL.16529.py +++ /dev/null @@ -1,1860 +0,0 @@ -# Distributed under the terms of the MIT License - -""" -Module for reading Lobster output files. For more information -on LOBSTER see www.cohp.de. -""" - -from __future__ import annotations - -import collections -import fnmatch -import os -import re -import warnings -from collections import defaultdict -from typing import Any - -import numpy as np -from monty.io import zopen - -from pymatgen.core.structure import Structure -from pymatgen.electronic_structure.bandstructure import LobsterBandStructureSymmLine -from pymatgen.electronic_structure.core import Orbital, Spin -from pymatgen.electronic_structure.dos import Dos, LobsterCompleteDos -from pymatgen.io.vasp.inputs import Kpoints -from pymatgen.io.vasp.outputs import Vasprun, VolumetricData - -__author__ = "Janine George, Marco Esters" -__copyright__ = "Copyright 2017, The Materials Project" -__version__ = "0.2" -__maintainer__ = "Janine George, Marco Esters " -__email__ = "janine.george@uclouvain.be, esters@uoregon.edu" -__date__ = "Dec 13, 2017" - -MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - - -class Cohpcar: - """ - Class to read COHPCAR/COOPCAR/COBICAR files generated by LOBSTER. - - .. attribute: cohp_data - - Dict that contains the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have - a "length" key. - - .. attribute: efermi - - The Fermi energy in eV. - - .. attribute: energies - - Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - - .. attribute: is_spin_polarized - - Boolean to indicate if the calculation is spin polarized. - - .. attribute: orb_cohp - - orb_cohp[label] = {bond_data["orb_label"]: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}} - - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - are_cobis: Determines if the file is a list of COHPs or COBIs. - Default is False for COHPs. - - filename: Name of the COHPCAR file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "COOPCAR.lobster" - elif are_cobis: - filename = "COBICAR.lobster" - else: - filename = "COHPCAR.lobster" - - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - # The parameters line is the second line in a COHPCAR file. It - # contains all parameters that are needed to map the file. - parameters = contents[1].split() - # Subtract 1 to skip the average - num_bonds = int(parameters[0]) - 1 - self.efermi = float(parameters[-1]) - if int(parameters[1]) == 2: - spins = [Spin.up, Spin.down] - self.is_spin_polarized = True - else: - spins = [Spin.up] - self.is_spin_polarized = False - - # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] - cohp_data: dict[str, dict[str, Any]] = { - "average": { - "COHP": {spin: data[1 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - "ICOHP": {spin: data[2 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - } - } - - orb_cohp: dict[str, Any] = {} - # present for Lobster versions older than Lobster 2.2.0 - veryold = False - # the labeling had to be changed: there are more than one COHP for each atom combination - # this is done to make the labeling consistent with ICOHPLIST.lobster - bondnumber = 0 - for bond in range(num_bonds): - bond_data = self._get_bond_data(contents[3 + bond]) - - label = str(bondnumber) - - orbs = bond_data["orbitals"] - cohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 3] for s, spin in enumerate(spins)} - - icohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 4] for s, spin in enumerate(spins)} - if orbs is None: - bondnumber = bondnumber + 1 - label = str(bondnumber) - cohp_data[label] = { - "COHP": cohp, - "ICOHP": icohp, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - ) - else: - # present for Lobster versions older than Lobster 2.2.0 - if bondnumber == 0: - veryold = True - if veryold: - bondnumber += 1 - label = str(bondnumber) - - orb_cohp[label] = { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - - # present for lobster older than 2.2.0 - if veryold: - for bond_str in orb_cohp: - cohp_data[bond_str] = { - "COHP": None, - "ICOHP": None, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - self.orb_res_cohp = orb_cohp or None - self.cohp_data = cohp_data - - @staticmethod - def _get_bond_data(line: str) -> dict: - """ - Subroutine to extract bond label, site indices, and length from - a LOBSTER header line. The site indices are zero-based, so they - can be easily used with a Structure object. - - Example header line: No.4:Fe1->Fe9(2.4524893531900283) - Example header line for orbtial-resolved COHP: - No.1:Fe1[3p_x]->Fe2[3d_x^2-y^2](2.456180552772262) - - Args: - line: line in the COHPCAR header describing the bond. - - Returns: - Dict with the bond label, the bond length, a tuple of the site - indices, a tuple containing the orbitals (if orbital-resolved), - and a label for the orbitals (if orbital-resolved). - """ - - line_new = line.rsplit("(", 1) - length = float(line_new[-1][:-1]) - - sites = line_new[0].replace("->", ":").split(":")[1:3] - site_indices = tuple(int(re.split(r"\D+", site)[1]) - 1 for site in sites) - - if "[" in sites[0]: - orbs = [re.findall(r"\[(.*)\]", site)[0] for site in sites] - orb_label, orbitals = get_orb_from_str(orbs) - - else: - orbitals = None - orb_label = None - - bond_data = { - "length": length, - "sites": site_indices, - "orbitals": orbitals, - "orb_label": orb_label, - } - return bond_data - - -class Icohplist: - """ - Class to read ICOHPLIST/ICOOPLIST files generated by LOBSTER. - - .. attribute: are_coops - Boolean to indicate if the populations are COOPs, COHPs or COBIs. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Icohplist - Dict containing the listfile data of the form: - {bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...}} - - .. attribute: IcohpCollection - IcohpCollection Object - - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of ICOOPs. - Defaults to False for ICOHPs. - are_cobis: Determines if the file is a list of ICOBIs. - Defaults to False for ICOHPs. - filename: Name of the ICOHPLIST file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "ICOOPLIST.lobster" - elif are_cobis: - filename = "ICOBILIST.lobster" - else: - filename = "ICOHPLIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("ICOHPLIST file contains no data.") - - # Which Lobster version? - if len(data[0].split()) == 8: - version = "3.1.1" - elif len(data[0].split()) == 6: - version = "2.2.1" - warnings.warn("Please consider using the new Lobster version. See www.cohp.de.") - else: - raise ValueError - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - if "distance" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = True - else: - self.is_spin_polarized = False - - # check if orbitalwise ICOHPLIST - # include case when there is only one ICOHP!!! - if len(data) > 2 and "_" in data[1].split()[1]: - self.orbitalwise = True - else: - self.orbitalwise = False - - if self.orbitalwise: - data_without_orbitals = [] - data_orbitals = [] - for line in data: - if "_" not in line.split()[1]: - data_without_orbitals.append(line) - else: - data_orbitals.append(line) - - else: - data_without_orbitals = data - - if "distance" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("ICOHPLIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - list_labels = [] - list_atom1 = [] - list_atom2 = [] - list_length = [] - list_translation = [] - list_num = [] - list_icohp = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - icohp = {} - if version == "2.2.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - icohp[Spin.up] = float(line[4]) - num = int(line[5]) - translation = [0, 0, 0] - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[4]) - - elif version == "3.1.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - translation = [int(line[4]), int(line[5]), int(line[6])] - icohp[Spin.up] = float(line[7]) - num = int(1) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) - - list_labels.append(label) - list_atom1.append(atom1) - list_atom2.append(atom2) - list_length.append(length) - list_translation.append(translation) - list_num.append(num) - list_icohp.append(icohp) - - list_orb_icohp: list[dict] | None = None - if self.orbitalwise: - list_orb_icohp = [] - num_orbs = len(data_orbitals) // 2 if self.is_spin_polarized else len(data_orbitals) - - for i_data_orb in range(num_orbs): - data_orb = data_orbitals[i_data_orb] - icohp = {} - line = data_orb.split() - label = f"{line[0]}" - orbs = re.findall(r"_(.*?)(?=\s)", data_orb) - orb_label, orbitals = get_orb_from_str(orbs) - icohp[Spin.up] = float(line[7]) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_orbitals[num_orbs + i_data_orb].split()[7]) - - if len(list_orb_icohp) < int(label): - list_orb_icohp.append({orb_label: {"icohp": icohp, "orbitals": orbitals}}) - else: - list_orb_icohp[int(label) - 1][orb_label] = {"icohp": icohp, "orbitals": orbitals} - - # to avoid circular dependencies - from pymatgen.electronic_structure.cohp import IcohpCollection - - self._icohpcollection = IcohpCollection( - are_coops=are_coops, - are_cobis=are_cobis, - list_labels=list_labels, - list_atom1=list_atom1, - list_atom2=list_atom2, - list_length=list_length, - list_translation=list_translation, - list_num=list_num, - list_icohp=list_icohp, - is_spin_polarized=self.is_spin_polarized, - list_orb_icohp=list_orb_icohp, - ) - - @property - def icohplist(self) -> dict[Any, dict[str, Any]]: - """ - Returns: icohplist compatible with older version of this class - """ - icohplist_new = {} - for key, value in self._icohpcollection._icohplist.items(): - icohplist_new[key] = { - "length": value._length, - "number_of_bonds": value._num, - "icohp": value._icohp, - "translation": value._translation, - "orbitals": value._orbitals, - } - return icohplist_new - - @property - def icohpcollection(self): - """ - Returns: IcohpCollection object - """ - return self._icohpcollection - - -class Ncicobilist: - """ - Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Ncicobilist - Dict containing the listfile data of the form: - {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, - "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, - "interaction_type": type of the multi-center interaction - - """ - - def __init__(self, filename: str | None = None): - """ - Args: - filename: Name of the NcICOBILIST file. - """ - - if filename is None: - warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") - filename = "NcICOBILIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("NcICOBILIST file contains no data.") - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - if "spin" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = True - else: - self.is_spin_polarized = False - - # check if orbitalwise NcICOBILIST - # include case when there is only one NcICOBI - for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed - if len(data) > 2 and "s]" in str(entry.split()[3:]): - self.orbitalwise = True - warnings.warn( - "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " - + "information is not read!" - ) - break # condition has only to be met once - else: - self.orbitalwise = False - - if self.orbitalwise: - data_without_orbitals = [] - for line in data: - if "_" not in str(line.split()[3:]) and "s]" not in str(line.split()[3:]): - data_without_orbitals.append(line) - else: - data_without_orbitals = data - - if "spin" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("NcICOBILIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - self.list_labels = [] - self.list_numofatoms = [] - self.list_ncicobi = [] - self.list_interactiontype = [] - self.list_num = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - ncicobi = {} - - label = f"{line[0]}" - numofatoms = str(line[1]) - ncicobi[Spin.up] = float(line[2]) - interactiontype = str(line[3:]).replace("'", "").replace(" ", "") - num = int(1) - - if self.is_spin_polarized: - ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) - - self.list_labels.append(label) - self.list_numofatoms.append(numofatoms) - self.list_ncicobi.append(ncicobi) - self.list_interactiontype.append(interactiontype) - self.list_num.append(num) - - # TODO: add functions to get orbital resolved NcICOBIs - - @property - def ncicobilist(self) -> dict[Any, dict[str, Any]]: - """ - Returns: ncicobilist. - """ - ncicobilist = {} - for key, _entry in enumerate(self.list_labels): - ncicobilist[str(key + 1)] = { - "number_of_atoms": int(self.list_numofatoms[key]), - "ncicobi": self.list_ncicobi[key], - "interaction_type": self.list_interactiontype[key], - } - - return ncicobilist - - -class Doscar: - """ - Class to deal with Lobster's projected DOS and local projected DOS. - The beforehand quantum-chemical calculation was performed with VASP - - .. attribute:: completedos - - LobsterCompleteDos Object - - .. attribute:: pdos - List of Dict including numpy arrays with pdos. Access as pdos[atomindex]['orbitalstring']['Spin.up/Spin.down'] - - .. attribute:: tdos - Dos Object of the total density of states - - .. attribute:: energies - numpy array of the energies at which the DOS was calculated (in eV, relative to Efermi) - - .. attribute:: tdensities - tdensities[Spin.up]: numpy array of the total density of states for the Spin.up contribution at each of the - energies - tdensities[Spin.down]: numpy array of the total density of states for the Spin.down contribution at each of the - energies - - if is_spin_polarized=False: - tdensities[Spin.up]: numpy array of the total density of states - - - .. attribute:: itdensities: - itdensities[Spin.up]: numpy array of the total density of states for the Spin.up contribution at each of the - energies - itdensities[Spin.down]: numpy array of the total density of states for the Spin.down contribution at each of the - energies - - if is_spin_polarized=False: - itdensities[Spin.up]: numpy array of the total density of states - - - .. attribute:: is_spin_polarized - Boolean. Tells if the system is spin polarized - """ - - def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str = "POSCAR", - dftprogram: str = "Vasp", - ): - """ - Args: - doscar: DOSCAR filename, typically "DOSCAR.lobster" - structure_file: for vasp, this is typically "POSCAR" - dftprogram: so far only "vasp" is implemented - """ - self._doscar = doscar - if dftprogram == "Vasp": - self._final_structure = Structure.from_file(structure_file) - - self._parse_doscar() - - def _parse_doscar(self): - doscar = self._doscar - - tdensities = {} - itdensities = {} - with zopen(doscar, "rt") as f: - natoms = int(f.readline().split()[0]) - efermi = float([f.readline() for nn in range(4)][3].split()[17]) - dos = [] - orbitals = [] - for _atom in range(natoms + 1): - line = f.readline() - ndos = int(line.split()[2]) - orbitals.append(line.split(";")[-1].split()) - line = f.readline().split() - cdos = np.zeros((ndos, len(line))) - cdos[0] = np.array(line) - for nd in range(1, ndos): - line = f.readline().split() - cdos[nd] = np.array(line) - dos.append(cdos) - doshere = np.array(dos[0]) - if len(doshere[0, :]) == 5: - self._is_spin_polarized = True - elif len(doshere[0, :]) == 3: - self._is_spin_polarized = False - else: - raise ValueError("There is something wrong with the DOSCAR. Can't extract spin polarization.") - energies = doshere[:, 0] - if not self._is_spin_polarized: - tdensities[Spin.up] = doshere[:, 1] - itdensities[Spin.up] = doshere[:, 2] - pdoss = [] - spin = Spin.up - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - orbnumber = orbnumber + 1 - pdoss.append(pdos) - else: - tdensities[Spin.up] = doshere[:, 1] - tdensities[Spin.down] = doshere[:, 2] - itdensities[Spin.up] = doshere[:, 3] - itdensities[Spin.down] = doshere[:, 4] - pdoss = [] - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - spin = Spin.down if j % 2 == 0 else Spin.up - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - if j % 2 == 0: - orbnumber = orbnumber + 1 - pdoss.append(pdos) - - self._efermi = efermi - self._pdos = pdoss - self._tdos = Dos(efermi, energies, tdensities) - self._energies = energies - self._tdensities = tdensities - self._itdensities = itdensities - final_struct = self._final_structure - - pdossneu = {final_struct[i]: pdos for i, pdos in enumerate(self._pdos)} - - self._completedos = LobsterCompleteDos(final_struct, self._tdos, pdossneu) - - @property - def completedos(self) -> LobsterCompleteDos: - """ - :return: CompleteDos - """ - return self._completedos - - @property - def pdos(self) -> list: - """ - :return: Projected DOS - """ - return self._pdos - - @property - def tdos(self) -> Dos: - """ - :return: Total DOS - """ - return self._tdos - - @property - def energies(self) -> np.ndarray: - """ - :return: Energies - """ - return self._energies - - @property - def tdensities(self) -> np.ndarray: - """ - :return: total densities as a np.ndarray - """ - return self._tdensities - - @property - def itdensities(self) -> np.ndarray: - """ - :return: integrated total densities as a np.ndarray - """ - return self._itdensities - - @property - def is_spin_polarized(self) -> bool: - """ - :return: Whether run is spin polarized. - """ - return self._is_spin_polarized - - -class Charge: - """ - Class to read CHARGE files generated by LOBSTER - - .. attribute: atomlist - List of atoms in CHARGE.lobster - .. attribute: types - List of types of atoms in CHARGE.lobster - .. attribute: Mulliken - List of Mulliken charges of atoms in CHARGE.lobster - .. attribute: Loewdin - List of Loewdin charges of atoms in CHARGE.Loewdin - .. attribute: num_atoms - Number of atoms in CHARGE.lobster - - """ - - def __init__(self, filename: str = "CHARGE.lobster"): - """ - Args: - filename: filename for the CHARGE file, typically "CHARGE.lobster" - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[3:-3] - if len(data) == 0: - raise OSError("CHARGES file contains no data.") - - self.num_atoms = len(data) - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.Mulliken: list[float] = [] - self.Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + line[0]) - self.types.append(line[1]) - self.Mulliken.append(float(line[2])) - self.Loewdin.append(float(line[3])) - - def get_structure_with_charges(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - Args: - structure_filename: filename of POSCAR - Returns: - Structure Object with Mulliken and Loewdin charges as site properties - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.Mulliken - Loewdin = self.Loewdin - site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - new_struct = struct.copy(site_properties=site_properties) - return new_struct - - -class Lobsterout: - """ - Class to read in the lobsterout and evaluate the spilling, save the basis, save warnings, save infos - - .. attribute: basis_functions - list of basis functions that were used in lobster run as strings - - .. attribute: basis_type - list of basis type that were used in lobster run as strings - - .. attribute: charge_spilling - list of charge spilling (first entry: result for spin 1, second entry: result for spin 2 or not present) - - .. attribute: dft_program - string representing the dft program used for the calculation of the wave function - - .. attribute: elements - list of strings of elements that were present in lobster calculation - - .. attribute: has_charge - Boolean, indicates that CHARGE.lobster is present - - .. attribute: has_cohpcar - Boolean, indicates that COHPCAR.lobster and ICOHPLIST.lobster are present - - .. attribute: has_madelung - Boolean, indicates that SitePotentials.lobster and MadelungEnergies.lobster are present - - .. attribute: has_coopcar - Boolean, indicates that COOPCAR.lobster and ICOOPLIST.lobster are present - - .. attribute: has_cobicar - Boolean, indicates that COBICAR.lobster and ICOBILIST.lobster are present - - .. attribute: has_doscar - Boolean, indicates that DOSCAR.lobster is present - - .. attribute: has_doscar_lso - Boolean, indicates that DOSCAR.LSO.lobster is present - - .. attribute: has_projection - Boolean, indicates that projectionData.lobster is present - - .. attribute: has_bandoverlaps - Boolean, indicates that bandOverlaps.lobster is present - - .. attribute: has_density_of_energies - Boolean, indicates that DensityOfEnergy.lobster is present - - .. attribute: has_fatbands - Boolean, indicates that fatband calculation was performed - - .. attribute: has_grosspopulation - Boolean, indicates that GROSSPOP.lobster is present - - .. attribute: info_lines - string with additional infos on the run - - .. attribute: info_orthonormalization - string with infos on orthonormalization - - .. attribute: is_restart_from_projection - Boolean that indicates that calculation was restartet from existing projection file - - .. attribute: lobster_version - string that indicates Lobster version - - .. attribute: number_of_spins - Integer indicating the number of spins - - .. attribute: number_of_threads - integer that indicates how many threads were used - - .. attribute: timing - dict with infos on timing - - .. attribute: total_spilling - list of values indicating the total spilling for spin channel 1 (and spin channel 2) - - .. attribute: warning_lines - string with all warnings - - """ - - # TODO: add tests for skipping COBI and madelung - # TODO: add tests for including COBI and madelung - def __init__(self, filename="lobsterout"): - """ - Args: - filename: filename of lobsterout - """ - # read in file - with zopen(filename, "rt") as f: - data = f.read().split("\n") # [3:-3] - if len(data) == 0: - raise OSError("lobsterout does not contain any data") - - # check if Lobster starts from a projection - self.is_restart_from_projection = "loading projection from projectionData.lobster..." in data - - self.lobster_version = self._get_lobster_version(data=data) - - self.number_of_threads = int(self._get_threads(data=data)) - self.dft_program = self._get_dft_program(data=data) - - self.number_of_spins = self._get_number_of_spins(data=data) - chargespilling, totalspilling = self._get_spillings(data=data, number_of_spins=self.number_of_spins) - self.charge_spilling = chargespilling - self.total_spilling = totalspilling - - ( - elements, - basistype, - basisfunctions, - ) = self._get_elements_basistype_basisfunctions(data=data) - self.elements = elements - self.basis_type = basistype - self.basis_functions = basisfunctions - - wall_time, user_time, sys_time = self._get_timing(data=data) - timing = {} - timing["wall_time"] = wall_time - timing["user_time"] = user_time - timing["sys_time"] = sys_time - self.timing = timing - - warninglines = self._get_all_warning_lines(data=data) - self.warning_lines = warninglines - - orthowarning = self._get_warning_orthonormalization(data=data) - self.info_orthonormalization = orthowarning - - infos = self._get_all_info_lines(data=data) - self.info_lines = infos - - self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data - self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data - ) - self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data - ) - self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data - ) - self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data - ) - - self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data - self.has_projection = "saving projection to projectionData.lobster..." in data - self.has_bandoverlaps = "WARNING: I dumped the band overlap matrices to the file bandOverlaps.lobster." in data - self.has_fatbands = self._has_fatband(data=data) - self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data - self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data - self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data - ) - - def get_doc(self): - """ - Returns: LobsterDict with all the information stored in lobsterout - """ - LobsterDict = {} - # check if Lobster starts from a projection - LobsterDict["restart_from_projection"] = self.is_restart_from_projection - LobsterDict["lobster_version"] = self.lobster_version - LobsterDict["threads"] = self.number_of_threads - LobsterDict["dft_program"] = self.dft_program - - LobsterDict["charge_spilling"] = self.charge_spilling - LobsterDict["total_spilling"] = self.total_spilling - - LobsterDict["elements"] = self.elements - LobsterDict["basis_type"] = self.basis_type - LobsterDict["basis_functions"] = self.basis_functions - - LobsterDict["timing"] = self.timing - - LobsterDict["warning_lines"] = self.warning_lines - - LobsterDict["info_orthonormalization"] = self.info_orthonormalization - - LobsterDict["info_lines"] = self.info_lines - - LobsterDict["has_doscar"] = self.has_doscar - LobsterDict["has_doscar_lso"] = self.has_doscar_lso - LobsterDict["has_cohpcar"] = self.has_cohpcar - LobsterDict["has_coopcar"] = self.has_coopcar - LobsterDict["has_cobicar"] = self.has_cobicar - LobsterDict["has_charge"] = self.has_charge - LobsterDict["has_madelung"] = self.has_madelung - LobsterDict["has_projection"] = self.has_projection - LobsterDict["has_bandoverlaps"] = self.has_bandoverlaps - LobsterDict["has_fatbands"] = self.has_fatbands - LobsterDict["has_grosspopulation"] = self.has_grosspopulation - LobsterDict["has_density_of_energies"] = self.has_density_of_energies - - return LobsterDict - - @staticmethod - def _get_lobster_version(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[0] == "LOBSTER": - return splitrow[1] - raise RuntimeError("Version not found.") - - @staticmethod - def _has_fatband(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[1] == "FatBand": - return True - return False - - @staticmethod - def _get_dft_program(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 4 and splitrow[3] == "program...": - return splitrow[4] - return None - - @staticmethod - def _get_number_of_spins(data): - if "spillings for spin channel 2" in data: - return 2 - return 1 - - @staticmethod - def _get_threads(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): - return splitrow[10] - raise ValueError("Threads not found.") - - @staticmethod - def _get_spillings(data, number_of_spins): - charge_spilling = [] - total_spilling = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 2 and splitrow[2] == "spilling:": - if splitrow[1] == "charge": - charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - if splitrow[1] == "total": - total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - - if len(charge_spilling) == number_of_spins and len(total_spilling) == number_of_spins: - break - - return charge_spilling, total_spilling - - @staticmethod - def _get_elements_basistype_basisfunctions(data): - begin = False - end = False - elements = [] - basistype = [] - basisfunctions = [] - for row in data: - if begin and not end: - splitrow = row.split() - if splitrow[0] not in [ - "INFO:", - "WARNING:", - "setting", - "calculating", - "post-processing", - "saving", - "spillings", - "writing", - ]: - elements.append(splitrow[0]) - basistype.append(splitrow[1].replace("(", "").replace(")", "")) - # last sign is a '' - basisfunctions.append(splitrow[2:]) - else: - end = True - if "setting up local basis functions..." in row: - begin = True - return elements, basistype, basisfunctions - - @staticmethod - def _get_timing(data): - # will give back wall, user and sys time - begin = False - # end=False - # time=[] - - for row in data: - splitrow = row.split() - if "finished" in splitrow: - begin = True - if begin: - if "wall" in splitrow: - wall_time = splitrow[2:10] - if "user" in splitrow: - user_time = splitrow[0:8] - if "sys" in splitrow: - sys_time = splitrow[0:8] - - wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} - user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} - sys_time_dict = {"h": sys_time[0], "min": sys_time[2], "s": sys_time[4], "ms": sys_time[6]} - - return wall_time_dict, user_time_dict, sys_time_dict - - @staticmethod - def _get_warning_orthonormalization(data): - orthowarning = [] - for row in data: - splitrow = row.split() - if "orthonormalized" in splitrow: - orthowarning.append(" ".join(splitrow[1:])) - return orthowarning - - @staticmethod - def _get_all_warning_lines(data): - ws = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "WARNING:": - ws.append(" ".join(splitrow[1:])) - return ws - - @staticmethod - def _get_all_info_lines(data): - infos = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "INFO:": - infos.append(" ".join(splitrow[1:])) - return infos - - -class Fatband: - """ - Reads in FATBAND_x_y.lobster files - - .. attribute: efermi - - efermi that was read in from vasprun.xml - - .. attribute: eigenvals - {Spin.up:[][],Spin.down:[][]}, the first index of the array - [][] refers to the band and the second to the index of the - kpoint. The kpoints are ordered according to the order of the - kpoints array. If the band structure is not spin polarized, we - only store one data set under Spin.up. - - .. attribute: is_spinpolarized - - Boolean that tells you whether this was a spin-polarized calculation - - .. attribute: kpoints_array - - list of kpoint as numpy arrays, in frac_coords of the given lattice by default - - .. attribute: label_dict - - (dict) of {} this link a kpoint (in frac coords or Cartesian coordinates depending on the coords). - - .. attribute: lattice - - lattice object of reciprocal lattice as read in from vasprun.xml - - .. attribute: nbands - - number of bands used in the calculation - - .. attribute: p_eigenvals - - dict of orbital projections as {spin: array of dict}. - The indices of the array are [band_index, kpoint_index]. - The dict is then built the following way: - {"string of element": "string of orbital as read in from FATBAND file"} - If the band structure is not spin polarized, we only store one data set under Spin.up. - - .. attribute: structure - - structure read in from vasprun.xml - """ - - def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): - """ - Args: - filenames (list or string): can be a list of file names or a path to a folder folder from which all - "FATBAND_*" files will be read - vasprun: corresponding vasprun file - Kpointsfile: KPOINTS file for bandstructure calculation, typically "KPOINTS" - """ - warnings.warn("Make sure all relevant FATBAND files were generated and read in!") - warnings.warn("Use Lobster 3.2.0 or newer for fatband calculations!") - - VASPRUN = Vasprun( - filename=vasprun, - ionic_step_skip=None, - ionic_step_offset=0, - parse_dos=True, - parse_eigen=False, - parse_projected_eigen=False, - parse_potcar_file=False, - occu_tol=1e-8, - exception_on_bad_xml=True, - ) - self.structure = VASPRUN.final_structure - self.lattice = self.structure.lattice.reciprocal_lattice - self.efermi = VASPRUN.efermi - kpoints_object = Kpoints.from_file(Kpointsfile) - - atomtype = [] - atomnames = [] - orbital_names = [] - - if not isinstance(filenames, list) or filenames is None: - filenames_new = [] - if filenames is None: - filenames = "." - for file in os.listdir(filenames): - if fnmatch.fnmatch(file, "FATBAND_*.lobster"): - filenames_new.append(os.path.join(filenames, file)) - filenames = filenames_new - if len(filenames) == 0: - raise ValueError("No FATBAND files in folder or given") - for filename in filenames: - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - atomnames.append(os.path.split(filename)[1].split("_")[1].capitalize()) - parameters = contents[0].split() - atomtype.append(re.split(r"[0-9]+", parameters[3])[0].capitalize()) - orbital_names.append(parameters[4]) - - # get atomtype orbital dict - atom_orbital_dict = {} - for iatom, atom in enumerate(atomnames): - if atom not in atom_orbital_dict: - atom_orbital_dict[atom] = [] - atom_orbital_dict[atom].append(orbital_names[iatom]) - # test if there are the same orbitals twice or if two different formats were used or if all necessary orbitals - # are there - for items in atom_orbital_dict.values(): - if len(set(items)) != len(items): - raise ValueError("The are two FATBAND files for the same atom and orbital. The program will stop.") - split = [] - for item in items: - split.append(item.split("_")[0]) - for number in collections.Counter(split).values(): - if number not in (1, 3, 5, 7): - raise ValueError( - "Make sure all relevant orbitals were generated and that no duplicates (2p and 2p_x) are " - "present" - ) - - kpoints_array = [] - for ifilename, filename in enumerate(filenames): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - if ifilename == 0: - self.nbands = int(parameters[6]) - self.number_kpts = kpoints_object.num_kpts - int(contents[1].split()[2]) + 1 - - if len(contents[1:]) == self.nbands + 2: - self.is_spinpolarized = False - elif len(contents[1:]) == self.nbands * 2 + 2: - self.is_spinpolarized = True - else: - linenumbers = [] - for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): - if line.split()[0] == "#": - linenumbers.append(iline) - - if ifilename == 0: - if len(linenumbers) == 2: - self.is_spinpolarized = True - else: - self.is_spinpolarized = False - - if ifilename == 0: - eigenvals = {} - eigenvals[Spin.up] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - if self.is_spinpolarized: - eigenvals[Spin.down] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - - p_eigenvals = {} - p_eigenvals[Spin.up] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - if self.is_spinpolarized: - p_eigenvals[Spin.down] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - ikpoint = -1 - for line in contents[1:-1]: - if line.split()[0] == "#": - KPOINT = np.array( - [ - float(line.split()[4]), - float(line.split()[5]), - float(line.split()[6]), - ] - ) - if ifilename == 0: - kpoints_array.append(KPOINT) - - linenumber = 0 - iband = 0 - ikpoint += 1 - if linenumber == self.nbands: - iband = 0 - if line.split()[0] != "#": - if linenumber < self.nbands: - if ifilename == 0: - eigenvals[Spin.up][iband][ikpoint] = float(line.split()[1]) + self.efermi - - p_eigenvals[Spin.up][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - if linenumber >= self.nbands and self.is_spinpolarized: - if ifilename == 0: - eigenvals[Spin.down][iband][ikpoint] = float(line.split()[1]) + self.efermi - p_eigenvals[Spin.down][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - - linenumber += 1 - iband += 1 - - self.kpoints_array = kpoints_array - self.eigenvals = eigenvals - self.p_eigenvals = p_eigenvals - - label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): - if label is not None: - label_dict[label] = kpoints_array[ilabel] - - self.label_dict = label_dict - - def get_bandstructure(self): - """ - Returns a LobsterBandStructureSymmLine object which can be plotted with a normal BSPlotter - """ - return LobsterBandStructureSymmLine( - kpoints=self.kpoints_array, - eigenvals=self.eigenvals, - lattice=self.lattice, - efermi=self.efermi, - labels_dict=self.label_dict, - structure=self.structure, - projections=self.p_eigenvals, - ) - - -class Bandoverlaps: - """ - Class to read in bandOverlaps.lobster files. These files are not created during every Lobster run. - .. attribute: bandoverlapsdict is a dict of the following form: - {spin:{"kpoint as string": {"maxDeviation": float that describes the max deviation, "matrix": 2D - array of the size number of bands times number of bands including the overlap matrices with } }} - - .. attribute: maxDeviation is a list of floats describing the maximal Deviation for each problematic kpoint - - """ - - def __init__(self, filename: str = "bandOverlaps.lobster"): - """ - Args: - filename: filename of the "bandOverlaps.lobster" file - """ - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - spin_numbers = [0, 1] if contents[0].split()[-1] == "0" else [1, 2] - - self._read(contents, spin_numbers) - - def _read(self, contents: list, spin_numbers: list): - """ - Will read in all contents of the file - Args: - contents: list of strings - spin_numbers: list of spin numbers depending on `Lobster` version. - """ - self.bandoverlapsdict: dict[Any, dict] = {} # Any is spin number 1 or -1 - self.max_deviation = [] - # This has to be done like this because there can be different numbers of problematic k-points per spin - for line in contents: - if f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[0]}" in line: - spin = Spin.up - elif f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[1]}" in line: - spin = Spin.down - elif "k-point" in line: - kpoint = line.split(" ") - kpoint_array = [] - for kpointel in kpoint: - if kpointel not in ["at", "k-point", ""]: - kpoint_array.append(str(kpointel)) - - elif "maxDeviation" in line: - if spin not in self.bandoverlapsdict: - self.bandoverlapsdict[spin] = {} - if " ".join(kpoint_array) not in self.bandoverlapsdict[spin]: - self.bandoverlapsdict[spin][" ".join(kpoint_array)] = {} - maxdev = line.split(" ")[2] - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["maxDeviation"] = float(maxdev) - self.max_deviation.append(float(maxdev)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"] = [] - - else: - overlaps = [] - for el in line.split(" "): - if el not in [""]: - overlaps.append(float(el)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"].append(overlaps) - - def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool: - """ - Will check if the maxDeviation from the ideal bandoverlap is smaller or equal to limit_maxDeviation - Args: - limit_maxDeviation: limit of the maxDeviation - Returns: - Boolean that will give you information about the quality of the projection - """ - return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) - - def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, - ) -> bool: - """ - Will check if the deviation from the ideal bandoverlap of all occupied bands is smaller or equal to - limit_deviation - - Args: - number_occ_bands_spin_up (int): number of occupied bands of spin up - number_occ_bands_spin_down (int): number of occupied bands of spin down - spin_polarized (bool): If True, then it was a spin polarized calculation - limit_deviation (float): limit of the maxDeviation - Returns: - Boolean that will give you information about the quality of the projection - """ - for matrix in self.bandoverlapsdict[Spin.up].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if iband1 < number_occ_bands_spin_up and iband2 < number_occ_bands_spin_up: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - - if spin_polarized: - for matrix in self.bandoverlapsdict[Spin.down].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if number_occ_bands_spin_down is not None: - if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - else: - ValueError("number_occ_bands_spin_down has to be specified") - return True - - -class Grosspop: - """ - Class to read in GROSSPOP.lobster files. - - .. attribute: list_dict_grosspop - which is a list of dicts including all information about the grosspopulations, one sample dict looks like this: - {'element': 'O', 'Mulliken GP': {'2s': '1.80', '2p_y': '1.83', '2p_z': '1.79', '2p_x': '1.75', 'total': '7.18'}, - 'Loewdin GP': {'2s': '1.60', '2p_y': '1.82', '2p_z': '1.77', '2p_x': '1.73', 'total': '6.92'}} - The 0. entry of the list refers to the first atom in GROSSPOP.lobster and so on. - """ - - def __init__(self, filename: str = "GROSSPOP.lobster"): - """ - Args: - filename: filename of the "GROSSPOP.lobster" file - """ - # opens file - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - self.list_dict_grosspop = [] - # transfers content of file to list of dict - for line in contents[3:]: - cleanline = [i for i in line.split(" ") if i != ""] - if len(cleanline) == 5: - smalldict = {} - smalldict["element"] = cleanline[1] - smalldict["Mulliken GP"] = {} - smalldict["Loewdin GP"] = {} - smalldict["Mulliken GP"][cleanline[2]] = float(cleanline[3]) - smalldict["Loewdin GP"][cleanline[2]] = float(cleanline[4]) - elif len(cleanline) > 0: - smalldict["Mulliken GP"][cleanline[0]] = float(cleanline[1]) - smalldict["Loewdin GP"][cleanline[0]] = float(cleanline[2]) - if "total" in cleanline[0]: - self.list_dict_grosspop.append(smalldict) - - def get_structure_with_total_grosspop(self, structure_filename: str) -> Structure: - """ - Get a Structure with Mulliken and Loewdin total grosspopulations as site properties - Args: - structure_filename (str): filename of POSCAR - Returns: - Structure Object with Mulliken and Loewdin total grosspopulations as site properties - """ - struct = Structure.from_file(structure_filename) - site_properties: dict[str, Any] = {} - mullikengp = [] - loewdingp = [] - for grosspop in self.list_dict_grosspop: - mullikengp.append(grosspop["Mulliken GP"]["total"]) - loewdingp.append(grosspop["Loewdin GP"]["total"]) - - site_properties = { - "Total Mulliken GP": mullikengp, - "Total Loewdin GP": loewdingp, - } - new_struct = struct.copy(site_properties=site_properties) - return new_struct - - -class Wavefunction: - """ - Class to read in wave function files from Lobster and transfer them into an object of the type VolumetricData - - .. attribute: grid - - grid for the wave function [Nx+1,Ny+1,Nz+1] - - .. attribute: points - - list of points - - .. attribute: real - - list of real part of wave function - - .. attribute: imaginary - - list of imaginary part of wave function - - .. attribute: distance - - list of distance to first point in wave function file - """ - - def __init__(self, filename, structure): - """ - Args: - filename: filename of wavecar file from Lobster - structure: Structure object (e.g., created by Structure.from_file("")) - """ - self.filename = filename - self.structure = structure - - ( - self.grid, - self.points, - self.real, - self.imaginary, - self.distance, - ) = Wavefunction._parse_file(filename) - - @staticmethod - def _parse_file(filename): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - points = [] - distance = [] - real = [] - imaginary = [] - splitline = contents[0].split() - grid = [int(splitline[7]), int(splitline[8]), int(splitline[9])] - for line in contents[1:]: - splitline = line.split() - if len(splitline) >= 6: - points.append([float(splitline[0]), float(splitline[1]), float(splitline[2])]) - distance.append(float(splitline[3])) - real.append(float(splitline[4])) - imaginary.append(float(splitline[5])) - - if not len(real) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - if not len(imaginary) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - return grid, points, real, imaginary, distance - - def set_volumetric_data(self, grid, structure): - """ - Will create the VolumetricData Objects - - Args: - grid: grid on which wavefunction was calculated, e.g. [1,2,2] - structure: Structure object - """ - Nx = grid[0] - 1 - Ny = grid[1] - 1 - Nz = grid[2] - 1 - a = structure.lattice.matrix[0] - b = structure.lattice.matrix[1] - c = structure.lattice.matrix[2] - new_x = [] - new_y = [] - new_z = [] - new_real = [] - new_imaginary = [] - new_density = [] - - runner = 0 - for x in range(0, Nx + 1): - for y in range(0, Ny + 1): - for z in range(0, Nz + 1): - x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] - y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] - z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - - if x != Nx and y != Ny and z != Nz: - if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) - ): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x.append(x_here) - new_y.append(y_here) - new_z.append(z_here) - - new_real.append(self.real[runner]) - new_imaginary.append(self.imaginary[runner]) - new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) - - runner += 1 - - self.final_real = np.reshape(new_real, [Nx, Ny, Nz]) - self.final_imaginary = np.reshape(new_imaginary, [Nx, Ny, Nz]) - self.final_density = np.reshape(new_density, [Nx, Ny, Nz]) - - self.volumetricdata_real = VolumetricData(structure, {"total": self.final_real}) - self.volumetricdata_imaginary = VolumetricData(structure, {"total": self.final_imaginary}) - self.volumetricdata_density = VolumetricData(structure, {"total": self.final_density}) - - def get_volumetricdata_real(self): - """ - Will return a VolumetricData object including the real part of the wave function - - Returns: VolumetricData object - """ - if not hasattr(self, "volumetricdata_real"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_real - - def get_volumetricdata_imaginary(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function - - Returns: VolumetricData object - """ - if not hasattr(self, "volumetricdata_imaginary"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_imaginary - - def get_volumetricdata_density(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function - - Returns: VolumetricData object - """ - if not hasattr(self, "volumetricdata_density"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_density - - def write_file(self, filename="WAVECAR.vasp", part="real"): - """ - Will save the wavefunction in a file format that can be read by VESTA - This will only work if the wavefunction from lobster was constructed with: - "printLCAORealSpaceWavefunction kpoint 1 coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 2 3 4 - 5 6 " - or similar (the whole unit cell has to be covered!) - - Args: - filename: Filename for the output, e.g., WAVECAR.vasp - part: which part of the wavefunction will be saved ("real" or "imaginary") - """ - if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") - ): - self.set_volumetric_data(self.grid, self.structure) - if part == "real": - self.volumetricdata_real.write_file(filename) - elif part == "imaginary": - self.volumetricdata_imaginary.write_file(filename) - elif part == "density": - self.volumetricdata_density.write_file(filename) - else: - raise ValueError('part can be only "real" or "imaginary" or "density"') - - -# madleung and sitepotential classes -class MadelungEnergies: - """ - Class to read MadelungEnergies.lobster files generated by LOBSTER - - .. attribute: madelungenergies_Mulliken - float that gives the madelung energy based on the Mulliken approach - .. attribute: madelungenergies_Loewdin - float that gives the madelung energy based on the Loewdin approach - .. attribute: ewald_splitting - Ewald Splitting parameter to compute SitePotentials - - """ - - def __init__(self, filename: str = "MadelungEnergies.lobster"): - """ - Args: - filename: filename of the "MadelungEnergies.lobster" file - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[5] - if len(data) == 0: - raise OSError("MadelungEnergies file contains no data.") - line = data.split() - self.ewald_splitting = float(line[0]) - self.madelungenergies_Mulliken = float(line[1]) - self.madelungenergies_Loewdin = float(line[2]) - - -class SitePotential: - """ - Class to read SitePotentials.lobster files generated by LOBSTER - - .. attribute: atomlist - List of atoms in SitePotentials.lobster - .. attribute: types - List of types of atoms in SitePotentials.lobster - .. attribute: num_atoms - Number of atoms in SitePotentials.lobster - .. attribute: sitepotentials_Mulliken - List of Mulliken potentials of sites in SitePotentials.lobster - .. attribute: sitepotentials_Loewdin - List of Loewdin potentials of sites in SitePotentials.lobster - .. attribute: madelung_Mulliken - float that gives the madelung energy based on the Mulliken approach - .. attribute: madelung_Loewdin - float that gives the madelung energy based on the Loewdin approach - .. attribute: ewald_splitting - Ewald Splitting parameter to compute SitePotentials - """ - - def __init__(self, filename: str = "SitePotentials.lobster"): - """ - Args: - filename: filename for the SitePotentials file, typically "SitePotentials.lobster" - """ - # site_potentials - with zopen(filename, "rt") as f: - data = f.read().split("\n") - if len(data) == 0: - raise OSError("SitePotentials file contains no data.") - - self.ewald_splitting = float(data[0].split()[9]) - - data = data[5:-1] - self.num_atoms = len(data) - 2 - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.sitepotentials_Mulliken: list[float] = [] - self.sitepotentials_Loewdin: list[float] = [] - for atom in range(0, self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + str(line[0])) - self.types.append(line[1]) - self.sitepotentials_Mulliken.append(float(line[2])) - self.sitepotentials_Loewdin.append(float(line[3])) - - self.madelungenergies_Mulliken = float(data[self.num_atoms + 1].split()[3]) - self.madelungenergies_Loewdin = float(data[self.num_atoms + 1].split()[4]) - - def get_structure_with_site_potentials(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - Args: - structure_filename: filename of POSCAR - Returns: - Structure Object with Mulliken and Loewdin charges as site properties - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.sitepotentials_Mulliken - Loewdin = self.sitepotentials_Loewdin - site_properties = { - "Mulliken Site Potentials (eV)": Mulliken, - "Loewdin Site Potentials (eV)": Loewdin, - } - new_struct = struct.copy(site_properties=site_properties) - return new_struct - - -def get_orb_from_str(orbs): - """ - - Args: - orbs: list of two str, e.g. ["2p_x", "3s"] - - Returns: - list of tw Orbital objects - - """ - # TODO: also useful for plotting of dos - orb_labs = [ - "s", - "p_y", - "p_z", - "p_x", - "d_xy", - "d_yz", - "d_z^2", - "d_xz", - "d_x^2-y^2", - "f_y(3x^2-y^2)", - "f_xyz", - "f_yz^2", - "f_z^3", - "f_xz^2", - "f_z(x^2-y^2)", - "f_x(x^2-3y^2)", - ] - orbitals = [(int(orb[0]), Orbital(orb_labs.index(orb[1:]))) for orb in orbs] - orb_label = f"{orbitals[0][0]}{orbitals[0][1].name}-{orbitals[1][0]}{orbitals[1][1].name}" # type: ignore - return orb_label, orbitals diff --git a/pymatgen/io/lobster/outputs.py.REMOTE.16529.py b/pymatgen/io/lobster/outputs.py.REMOTE.16529.py deleted file mode 100644 index 9acf023742a..00000000000 --- a/pymatgen/io/lobster/outputs.py.REMOTE.16529.py +++ /dev/null @@ -1,1589 +0,0 @@ -""" -Module for reading Lobster output files. For more information -on LOBSTER see www.cohp.de. -If you use this module, please cite: -J. George, G. Petretto, A. Naik, M. Esters, A. J. Jackson, R. Nelson, R. Dronskowski, G.-M. Rignanese, G. Hautier, -"Automated Bonding Analysis with Crystal Orbital Hamilton Populations", -ChemPlusChem 2022, e202200123, -DOI: 10.1002/cplu.202200123. -""" - -from __future__ import annotations - -import collections -import fnmatch -import os -import re -import warnings -from collections import defaultdict -from typing import TYPE_CHECKING, Any - -import numpy as np -from monty.io import zopen - -from pymatgen.core.structure import Structure -from pymatgen.electronic_structure.bandstructure import LobsterBandStructureSymmLine -from pymatgen.electronic_structure.core import Orbital, Spin -from pymatgen.electronic_structure.dos import Dos, LobsterCompleteDos -from pymatgen.io.vasp.inputs import Kpoints -from pymatgen.io.vasp.outputs import Vasprun, VolumetricData -from pymatgen.util.due import Doi, due - -if TYPE_CHECKING: - from pymatgen.core.structure import IStructure - -__author__ = "Janine George, Marco Esters" -__copyright__ = "Copyright 2017, The Materials Project" -__version__ = "0.2" -__maintainer__ = "Janine George " -__email__ = "janinegeorge.ulfen@gmail.com" -__date__ = "Dec 13, 2017" - -MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - -due.cite( - Doi("10.1002/cplu.202200123"), - description="Automated Bonding Analysis with Crystal Orbital Hamilton Populations", -) - - -class Cohpcar: - """Class to read COHPCAR/COOPCAR files generated by LOBSTER. - - Attributes: - cohp_data (dict[str, Dict[str, Any]]): A dictionary containing the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have a "length" key. - efermi (float): The Fermi energy in eV. - energies (Sequence[float]): Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - orb_cohp (dict[str, Dict[str, Dict[str, Any]]]): A dictionary containing the orbital-resolved COHPs of the form: - orb_cohp[label] = {bond_data["orb_label"]: { - "COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}, - } - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - are_cobis: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - - filename: Name of the COHPCAR file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "COOPCAR.lobster" - elif are_cobis: - filename = "COBICAR.lobster" - else: - filename = "COHPCAR.lobster" - - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - # The parameters line is the second line in a COHPCAR file. It - # contains all parameters that are needed to map the file. - parameters = contents[1].split() - # Subtract 1 to skip the average - num_bonds = int(parameters[0]) - 1 - self.efermi = float(parameters[-1]) - self.is_spin_polarized = int(parameters[1]) == 2 - spins = [Spin.up, Spin.down] if int(parameters[1]) == 2 else [Spin.up] - - # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] - cohp_data: dict[str, dict[str, Any]] = { - "average": { - "COHP": {spin: data[1 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - "ICOHP": {spin: data[2 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - } - } - - orb_cohp: dict[str, Any] = {} - # present for Lobster versions older than Lobster 2.2.0 - very_old = False - # the labeling had to be changed: there are more than one COHP for each atom combination - # this is done to make the labeling consistent with ICOHPLIST.lobster - bond_num = 0 - for bond in range(num_bonds): - bond_data = self._get_bond_data(contents[3 + bond]) - - label = str(bond_num) - - orbs = bond_data["orbitals"] - cohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 3] for s, spin in enumerate(spins)} - - icohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 4] for s, spin in enumerate(spins)} - if orbs is None: - bond_num = bond_num + 1 - label = str(bond_num) - cohp_data[label] = { - "COHP": cohp, - "ICOHP": icohp, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - ) - else: - # present for Lobster versions older than Lobster 2.2.0 - if bond_num == 0: - very_old = True - if very_old: - bond_num += 1 - label = str(bond_num) - - orb_cohp[label] = { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - - # present for lobster older than 2.2.0 - if very_old: - for bond_str in orb_cohp: - cohp_data[bond_str] = { - "COHP": None, - "ICOHP": None, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - self.orb_res_cohp = orb_cohp or None - self.cohp_data = cohp_data - - @staticmethod - def _get_bond_data(line: str) -> dict: - """ - Subroutine to extract bond label, site indices, and length from - a LOBSTER header line. The site indices are zero-based, so they - can be easily used with a Structure object. - - Example header line: No.4:Fe1->Fe9(2.4524893531900283) - Example header line for orbtial-resolved COHP: - No.1:Fe1[3p_x]->Fe2[3d_x^2-y^2](2.456180552772262) - - Args: - line: line in the COHPCAR header describing the bond. - - Returns: - Dict with the bond label, the bond length, a tuple of the site - indices, a tuple containing the orbitals (if orbital-resolved), - and a label for the orbitals (if orbital-resolved). - """ - line_new = line.rsplit("(", 1) - length = float(line_new[-1][:-1]) - - sites = line_new[0].replace("->", ":").split(":")[1:3] - site_indices = tuple(int(re.split(r"\D+", site)[1]) - 1 for site in sites) - - if "[" in sites[0]: - orbs = [re.findall(r"\[(.*)\]", site)[0] for site in sites] - orb_label, orbitals = get_orb_from_str(orbs) - - else: - orbitals = orb_label = None - - return { - "length": length, - "sites": site_indices, - "orbitals": orbitals, - "orb_label": orb_label, - } - - -class Icohplist: - """ - Class to read ICOHPLIST/ICOOPLIST files generated by LOBSTER. - - Attributes: - are_coops (bool): Indicates whether the object is consisting of COOPs. - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - Icohplist (dict[str, Dict[str, Union[float, int, Dict[Spin, float]]]]): Dict containing the - listfile data of the form: { - bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...} - } - IcohpCollection (IcohpCollection): IcohpCollection Object. - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of ICOOPs. - Defaults to False for ICOHPs. - are_cobis: Determines if the file is a list of ICOBIs. - Defaults to False for ICOHPs. - filename: Name of the ICOHPLIST file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "ICOOPLIST.lobster" - elif are_cobis: - filename = "ICOBILIST.lobster" - else: - filename = "ICOHPLIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("ICOHPLIST file contains no data.") - - # Which Lobster version? - if len(data[0].split()) == 8: - version = "3.1.1" - elif len(data[0].split()) == 6: - version = "2.2.1" - warnings.warn("Please consider using the new Lobster version. See www.cohp.de.") - else: - raise ValueError - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = "distance" in data[len(data) // 2] - - # check if orbitalwise ICOHPLIST - # include case when there is only one ICOHP!!! - self.orbitalwise = len(data) > 2 and "_" in data[1].split()[1] - - if self.orbitalwise: - data_without_orbitals = [] - data_orbitals = [] - for line in data: - if "_" not in line.split()[1]: - data_without_orbitals.append(line) - else: - data_orbitals.append(line) - - else: - data_without_orbitals = data - - if "distance" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("ICOHPLIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - list_labels = [] - list_atom1 = [] - list_atom2 = [] - list_length = [] - list_translation = [] - list_num = [] - list_icohp = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - icohp = {} - if version == "2.2.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - icohp[Spin.up] = float(line[4]) - num = int(line[5]) - translation = [0, 0, 0] - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[4]) - - elif version == "3.1.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - translation = [int(line[4]), int(line[5]), int(line[6])] - icohp[Spin.up] = float(line[7]) - num = 1 - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) - - list_labels.append(label) - list_atom1.append(atom1) - list_atom2.append(atom2) - list_length.append(length) - list_translation.append(translation) - list_num.append(num) - list_icohp.append(icohp) - - list_orb_icohp: list[dict] | None = None - if self.orbitalwise: - list_orb_icohp = [] - num_orbs = len(data_orbitals) // 2 if self.is_spin_polarized else len(data_orbitals) - - for i_data_orb in range(num_orbs): - data_orb = data_orbitals[i_data_orb] - icohp = {} - line = data_orb.split() - label = f"{line[0]}" - orbs = re.findall(r"_(.*?)(?=\s)", data_orb) - orb_label, orbitals = get_orb_from_str(orbs) - icohp[Spin.up] = float(line[7]) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_orbitals[num_orbs + i_data_orb].split()[7]) - - if len(list_orb_icohp) < int(label): - list_orb_icohp.append({orb_label: {"icohp": icohp, "orbitals": orbitals}}) - else: - list_orb_icohp[int(label) - 1][orb_label] = {"icohp": icohp, "orbitals": orbitals} - - # to avoid circular dependencies - from pymatgen.electronic_structure.cohp import IcohpCollection - - self._icohpcollection = IcohpCollection( - are_coops=are_coops, - are_cobis=are_cobis, - list_labels=list_labels, - list_atom1=list_atom1, - list_atom2=list_atom2, - list_length=list_length, - list_translation=list_translation, - list_num=list_num, - list_icohp=list_icohp, - is_spin_polarized=self.is_spin_polarized, - list_orb_icohp=list_orb_icohp, - ) - - @property - def icohplist(self) -> dict[Any, dict[str, Any]]: - """Returns: icohplist compatible with older version of this class.""" - icohplist_new = {} - for key, value in self._icohpcollection._icohplist.items(): - icohplist_new[key] = { - "length": value._length, - "number_of_bonds": value._num, - "icohp": value._icohp, - "translation": value._translation, - "orbitals": value._orbitals, - } - return icohplist_new - - @property - def icohpcollection(self): - """Returns: IcohpCollection object.""" - return self._icohpcollection - - -class Doscar: - """ - Class to deal with Lobster's projected DOS and local projected DOS. - The beforehand quantum-chemical calculation was performed with VASP. - - Attributes: - completedos (LobsterCompleteDos): LobsterCompleteDos Object. - pdos (list): List of Dict including numpy arrays with pdos. Access as - pdos[atomindex]['orbitalstring']['Spin.up/Spin.down']. - tdos (Dos): Dos Object of the total density of states. - energies (numpy.ndarray): Numpy array of the energies at which the DOS was calculated - (in eV, relative to Efermi). - tdensities (dict): tdensities[Spin.up]: numpy array of the total density of states for - the Spin.up contribution at each of the energies. tdensities[Spin.down]: numpy array - of the total density of states for the Spin.down contribution at each of the energies. - If is_spin_polarized=False, tdensities[Spin.up]: numpy array of the total density of states. - itdensities (dict): itdensities[Spin.up]: numpy array of the total density of states for - the Spin.up contribution at each of the energies. itdensities[Spin.down]: numpy array - of the total density of states for the Spin.down contribution at each of the energies. - If is_spin_polarized=False, itdensities[Spin.up]: numpy array of the total density of states. - is_spin_polarized (bool): Boolean. Tells if the system is spin polarized. - """ - - def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str | None = "POSCAR", - structure: IStructure | Structure | None = None, - ): - """ - Args: - doscar: DOSCAR filename, typically "DOSCAR.lobster" - structure_file: for vasp, this is typically "POSCAR" - structure: instead of a structure file, the structure can be given - directly. structure_file will be preferred. - """ - self._doscar = doscar - - self._final_structure = Structure.from_file(structure_file) if structure_file is not None else structure - - self._parse_doscar() - - def _parse_doscar(self): - doscar = self._doscar - - tdensities = {} - itdensities = {} - with zopen(doscar, "rt") as f: - natoms = int(f.readline().split()[0]) - efermi = float([f.readline() for nn in range(4)][3].split()[17]) - dos = [] - orbitals = [] - for _atom in range(natoms + 1): - line = f.readline() - ndos = int(line.split()[2]) - orbitals.append(line.split(";")[-1].split()) - line = f.readline().split() - cdos = np.zeros((ndos, len(line))) - cdos[0] = np.array(line) - for nd in range(1, ndos): - line = f.readline().split() - cdos[nd] = np.array(line) - dos.append(cdos) - doshere = np.array(dos[0]) - if len(doshere[0, :]) == 5: - self._is_spin_polarized = True - elif len(doshere[0, :]) == 3: - self._is_spin_polarized = False - else: - raise ValueError("There is something wrong with the DOSCAR. Can't extract spin polarization.") - energies = doshere[:, 0] - if not self._is_spin_polarized: - tdensities[Spin.up] = doshere[:, 1] - itdensities[Spin.up] = doshere[:, 2] - pdoss = [] - spin = Spin.up - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - orbnumber = orbnumber + 1 - pdoss.append(pdos) - else: - tdensities[Spin.up] = doshere[:, 1] - tdensities[Spin.down] = doshere[:, 2] - itdensities[Spin.up] = doshere[:, 3] - itdensities[Spin.down] = doshere[:, 4] - pdoss = [] - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - spin = Spin.down if j % 2 == 0 else Spin.up - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - if j % 2 == 0: - orbnumber = orbnumber + 1 - pdoss.append(pdos) - - self._efermi = efermi - self._pdos = pdoss - self._tdos = Dos(efermi, energies, tdensities) - self._energies = energies - self._tdensities = tdensities - self._itdensities = itdensities - final_struct = self._final_structure - - pdossneu = {final_struct[i]: pdos for i, pdos in enumerate(self._pdos)} - - self._completedos = LobsterCompleteDos(final_struct, self._tdos, pdossneu) - - @property - def completedos(self) -> LobsterCompleteDos: - """LobsterCompleteDos""" - return self._completedos - - @property - def pdos(self) -> list: - """Projected DOS""" - return self._pdos - - @property - def tdos(self) -> Dos: - """Total DOS""" - return self._tdos - - @property - def energies(self) -> np.ndarray: - """Energies""" - return self._energies - - @property - def tdensities(self) -> np.ndarray: - """total densities as a np.ndarray""" - return self._tdensities - - @property - def itdensities(self) -> np.ndarray: - """integrated total densities as a np.ndarray""" - return self._itdensities - - @property - def is_spin_polarized(self) -> bool: - """Whether run is spin polarized.""" - return self._is_spin_polarized - - -class Charge: - """ - Class to read CHARGE files generated by LOBSTER. - - Attributes: - atomlist (list[str]): List of atoms in CHARGE.lobster. - types (list[str]): List of types of atoms in CHARGE.lobster. - Mulliken (list[float]): List of Mulliken charges of atoms in CHARGE.lobster. - Loewdin (list[float]): List of Loewdin charges of atoms in CHARGE.Loewdin. - num_atoms (int): Number of atoms in CHARGE.lobster. - """ - - def __init__(self, filename: str = "CHARGE.lobster"): - """ - Args: - filename: filename for the CHARGE file, typically "CHARGE.lobster". - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[3:-3] - if len(data) == 0: - raise OSError("CHARGES file contains no data.") - - self.num_atoms = len(data) - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.Mulliken: list[float] = [] - self.Loewdin: list[float] = [] - for atom in range(self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + line[0]) - self.types.append(line[1]) - self.Mulliken.append(float(line[2])) - self.Loewdin.append(float(line[3])) - - def get_structure_with_charges(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - - Args: - structure_filename: filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin charges as site properties. - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.Mulliken - Loewdin = self.Loewdin - site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - return struct.copy(site_properties=site_properties) - - -class Lobsterout: - """ - Class to read in the lobsterout and evaluate the spilling, save the basis, save warnings, save infos. - - Attributes: - basis_functions (list[str]): List of basis functions that were used in lobster run as strings. - basis_type (list[str]): List of basis type that were used in lobster run as strings. - charge_spilling (list[float]): List of charge spilling (first entry: result for spin 1, - second entry: result for spin 2 or not present). - dft_program (str): String representing the DFT program used for the calculation of the wave function. - elements (list[str]): List of strings of elements that were present in lobster calculation. - has_charge (bool): Whether CHARGE.lobster is present. - has_cohpcar (bool): Whether COHPCAR.lobster and ICOHPLIST.lobster are present. - has_madelung (bool): Whether SitePotentials.lobster and MadelungEnergies.lobster are present. - has_coopcar (bool): Whether COOPCAR.lobster and ICOOPLIST.lobster are present. - has_cobicar (bool): Whether COBICAR.lobster and ICOBILIST.lobster are present. - has_doscar (bool): Whether DOSCAR.lobster is present. - has_doscar_lso (bool): Whether DOSCAR.LSO.lobster is present. - has_projection (bool): Whether projectionData.lobster is present. - has_bandoverlaps (bool): Whether bandOverlaps.lobster is present. - has_density_of_energies (bool): Whether DensityOfEnergy.lobster is present. - has_fatbands (bool): Whether fatband calculation was performed. - has_grosspopulation (bool): Whether GROSSPOP.lobster is present. - info_lines (str): String with additional infos on the run. - info_orthonormalization (str): String with infos on orthonormalization. - is_restart_from_projection (bool): Boolean that indicates that calculation was restarted - from existing projection file. - lobster_version (str): String that indicates Lobster version. - number_of_spins (int): Integer indicating the number of spins. - number_of_threads (int): Integer that indicates how many threads were used. - timing (dict[str, float]): Dictionary with infos on timing. - total_spilling (list[float]): List of values indicating the total spilling for spin - channel 1 (and spin channel 2). - warning_lines (str): String with all warnings. - """ - - # TODO: add tests for skipping COBI and madelung - # TODO: add tests for including COBI and madelung - def __init__(self, filename="lobsterout"): - """ - Args: - filename: filename of lobsterout. - """ - # read in file - with zopen(filename, "rt") as f: - data = f.read().split("\n") # [3:-3] - if len(data) == 0: - raise OSError("lobsterout does not contain any data") - - # check if Lobster starts from a projection - self.is_restart_from_projection = "loading projection from projectionData.lobster..." in data - - self.lobster_version = self._get_lobster_version(data=data) - - self.number_of_threads = int(self._get_threads(data=data)) - self.dft_program = self._get_dft_program(data=data) - - self.number_of_spins = self._get_number_of_spins(data=data) - chargespilling, totalspilling = self._get_spillings(data=data, number_of_spins=self.number_of_spins) - self.charge_spilling = chargespilling - self.total_spilling = totalspilling - - elements, basistype, basisfunctions = self._get_elements_basistype_basisfunctions(data=data) - self.elements = elements - self.basis_type = basistype - self.basis_functions = basisfunctions - - wall_time, user_time, sys_time = self._get_timing(data=data) - timing = {} - timing["wall_time"] = wall_time - timing["user_time"] = user_time - timing["sys_time"] = sys_time - self.timing = timing - - warninglines = self._get_all_warning_lines(data=data) - self.warning_lines = warninglines - - orthowarning = self._get_warning_orthonormalization(data=data) - self.info_orthonormalization = orthowarning - - infos = self._get_all_info_lines(data=data) - self.info_lines = infos - - self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data - self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data - ) - self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data - ) - self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data - ) - self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data - ) - - self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data - self.has_projection = "saving projection to projectionData.lobster..." in data - self.has_bandoverlaps = "WARNING: I dumped the band overlap matrices to the file bandOverlaps.lobster." in data - self.has_fatbands = self._has_fatband(data=data) - self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data - self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data - self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data - ) - - def get_doc(self): - """Returns: LobsterDict with all the information stored in lobsterout.""" - LobsterDict = {} - # check if Lobster starts from a projection - LobsterDict["restart_from_projection"] = self.is_restart_from_projection - LobsterDict["lobster_version"] = self.lobster_version - LobsterDict["threads"] = self.number_of_threads - LobsterDict["dft_program"] = self.dft_program - - LobsterDict["charge_spilling"] = self.charge_spilling - LobsterDict["total_spilling"] = self.total_spilling - - LobsterDict["elements"] = self.elements - LobsterDict["basis_type"] = self.basis_type - LobsterDict["basis_functions"] = self.basis_functions - - LobsterDict["timing"] = self.timing - - LobsterDict["warning_lines"] = self.warning_lines - - LobsterDict["info_orthonormalization"] = self.info_orthonormalization - - LobsterDict["info_lines"] = self.info_lines - - LobsterDict["has_doscar"] = self.has_doscar - LobsterDict["has_doscar_lso"] = self.has_doscar_lso - LobsterDict["has_cohpcar"] = self.has_cohpcar - LobsterDict["has_coopcar"] = self.has_coopcar - LobsterDict["has_cobicar"] = self.has_cobicar - LobsterDict["has_charge"] = self.has_charge - LobsterDict["has_madelung"] = self.has_madelung - LobsterDict["has_projection"] = self.has_projection - LobsterDict["has_bandoverlaps"] = self.has_bandoverlaps - LobsterDict["has_fatbands"] = self.has_fatbands - LobsterDict["has_grosspopulation"] = self.has_grosspopulation - LobsterDict["has_density_of_energies"] = self.has_density_of_energies - - return LobsterDict - - @staticmethod - def _get_lobster_version(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[0] == "LOBSTER": - return splitrow[1] - raise RuntimeError("Version not found.") - - @staticmethod - def _has_fatband(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[1] == "FatBand": - return True - return False - - @staticmethod - def _get_dft_program(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 4 and splitrow[3] == "program...": - return splitrow[4] - return None - - @staticmethod - def _get_number_of_spins(data): - if "spillings for spin channel 2" in data: - return 2 - return 1 - - @staticmethod - def _get_threads(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): - return splitrow[10] - raise ValueError("Threads not found.") - - @staticmethod - def _get_spillings(data, number_of_spins): - charge_spilling = [] - total_spilling = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 2 and splitrow[2] == "spilling:": - if splitrow[1] == "charge": - charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - if splitrow[1] == "total": - total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - - if len(charge_spilling) == number_of_spins and len(total_spilling) == number_of_spins: - break - - return charge_spilling, total_spilling - - @staticmethod - def _get_elements_basistype_basisfunctions(data): - begin = False - end = False - elements = [] - basistype = [] - basisfunctions = [] - for row in data: - if begin and not end: - splitrow = row.split() - if splitrow[0] not in [ - "INFO:", - "WARNING:", - "setting", - "calculating", - "post-processing", - "saving", - "spillings", - "writing", - ]: - elements.append(splitrow[0]) - basistype.append(splitrow[1].replace("(", "").replace(")", "")) - # last sign is a '' - basisfunctions.append(splitrow[2:]) - else: - end = True - if "setting up local basis functions..." in row: - begin = True - return elements, basistype, basisfunctions - - @staticmethod - def _get_timing(data): - # will give back wall, user and sys time - begin = False - # end=False - # time=[] - - for row in data: - splitrow = row.split() - if "finished" in splitrow: - begin = True - if begin: - if "wall" in splitrow: - wall_time = splitrow[2:10] - if "user" in splitrow: - user_time = splitrow[0:8] - if "sys" in splitrow: - sys_time = splitrow[0:8] - - wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} - user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} - sys_time_dict = {"h": sys_time[0], "min": sys_time[2], "s": sys_time[4], "ms": sys_time[6]} - - return wall_time_dict, user_time_dict, sys_time_dict - - @staticmethod - def _get_warning_orthonormalization(data): - orthowarning = [] - for row in data: - splitrow = row.split() - if "orthonormalized" in splitrow: - orthowarning.append(" ".join(splitrow[1:])) - return orthowarning - - @staticmethod - def _get_all_warning_lines(data): - ws = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "WARNING:": - ws.append(" ".join(splitrow[1:])) - return ws - - @staticmethod - def _get_all_info_lines(data): - infos = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "INFO:": - infos.append(" ".join(splitrow[1:])) - return infos - - -class Fatband: - """ - Reads in FATBAND_x_y.lobster files. - - Attributes: - efermi (float): Fermi energy read in from vasprun.xml. - eigenvals (dict[Spin, np.ndarray]): Eigenvalues as a dictionary of numpy arrays of shape (nbands, nkpoints). - The first index of the array refers to the band and the second to the index of the kpoint. - The kpoints are ordered according to the order of the kpoints_array attribute. - If the band structure is not spin polarized, we only store one data set under Spin.up. - is_spin_polarized (bool): Boolean that tells you whether this was a spin-polarized calculation. - kpoints_array (list[np.ndarray]): List of kpoints as numpy arrays, in frac_coords of the given - lattice by default. - label_dict (dict[str, Union[str, np.ndarray]]): Dictionary that links a kpoint (in frac coords or Cartesian - coordinates depending on the coords attribute) to a label. - lattice (Lattice): Lattice object of reciprocal lattice as read in from vasprun.xml. - nbands (int): Number of bands used in the calculation. - p_eigenvals (dict[Spin, np.ndarray]): Dictionary of orbital projections as {spin: array of dict}. - The indices of the array are [band_index, kpoint_index]. - The dict is then built the following way: {"string of element": "string of orbital as read in - from FATBAND file"}. If the band structure is not spin polarized, we only store one data set under Spin.up. - structure (Structure): Structure read in from vasprun.xml. - """ - - def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): - """ - Args: - filenames (list or string): can be a list of file names or a path to a folder from which all - "FATBAND_*" files will be read - vasprun: corresponding vasprun file - Kpointsfile: KPOINTS file for bandstructure calculation, typically "KPOINTS". - """ - warnings.warn("Make sure all relevant FATBAND files were generated and read in!") - warnings.warn("Use Lobster 3.2.0 or newer for fatband calculations!") - - vasp_run = Vasprun( - filename=vasprun, - ionic_step_skip=None, - ionic_step_offset=0, - parse_dos=True, - parse_eigen=False, - parse_projected_eigen=False, - parse_potcar_file=False, - occu_tol=1e-8, - exception_on_bad_xml=True, - ) - self.structure = vasp_run.final_structure - self.lattice = self.structure.lattice.reciprocal_lattice - self.efermi = vasp_run.efermi - kpoints_object = Kpoints.from_file(Kpointsfile) - - atomtype = [] - atomnames = [] - orbital_names = [] - - if not isinstance(filenames, list) or filenames is None: - filenames_new = [] - if filenames is None: - filenames = "." - for file in os.listdir(filenames): - if fnmatch.fnmatch(file, "FATBAND_*.lobster"): - filenames_new.append(os.path.join(filenames, file)) - filenames = filenames_new - if len(filenames) == 0: - raise ValueError("No FATBAND files in folder or given") - for filename in filenames: - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - atomnames.append(os.path.split(filename)[1].split("_")[1].capitalize()) - parameters = contents[0].split() - atomtype.append(re.split(r"[0-9]+", parameters[3])[0].capitalize()) - orbital_names.append(parameters[4]) - - # get atomtype orbital dict - atom_orbital_dict = {} - for iatom, atom in enumerate(atomnames): - if atom not in atom_orbital_dict: - atom_orbital_dict[atom] = [] - atom_orbital_dict[atom].append(orbital_names[iatom]) - # test if there are the same orbitals twice or if two different formats were used or if all necessary orbitals - # are there - for items in atom_orbital_dict.values(): - if len(set(items)) != len(items): - raise ValueError("The are two FATBAND files for the same atom and orbital. The program will stop.") - split = [] - for item in items: - split.append(item.split("_")[0]) - for number in collections.Counter(split).values(): - if number not in (1, 3, 5, 7): - raise ValueError( - "Make sure all relevant orbitals were generated and that no duplicates (2p and 2p_x) are " - "present" - ) - - kpoints_array = [] - for ifilename, filename in enumerate(filenames): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - if ifilename == 0: - self.nbands = int(parameters[6]) - self.number_kpts = kpoints_object.num_kpts - int(contents[1].split()[2]) + 1 - - if len(contents[1:]) == self.nbands + 2: - self.is_spinpolarized = False - elif len(contents[1:]) == self.nbands * 2 + 2: - self.is_spinpolarized = True - else: - linenumbers = [] - for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): - if line.split()[0] == "#": - linenumbers.append(iline) - - if ifilename == 0: - self.is_spinpolarized = len(linenumbers) == 2 - - if ifilename == 0: - eigenvals = {} - eigenvals[Spin.up] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - if self.is_spinpolarized: - eigenvals[Spin.down] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - - p_eigenvals = {} - p_eigenvals[Spin.up] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - if self.is_spinpolarized: - p_eigenvals[Spin.down] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - ikpoint = -1 - for line in contents[1:-1]: - if line.split()[0] == "#": - KPOINT = np.array( - [ - float(line.split()[4]), - float(line.split()[5]), - float(line.split()[6]), - ] - ) - if ifilename == 0: - kpoints_array.append(KPOINT) - - linenumber = 0 - iband = 0 - ikpoint += 1 - if linenumber == self.nbands: - iband = 0 - if line.split()[0] != "#": - if linenumber < self.nbands: - if ifilename == 0: - eigenvals[Spin.up][iband][ikpoint] = float(line.split()[1]) + self.efermi - - p_eigenvals[Spin.up][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - if linenumber >= self.nbands and self.is_spinpolarized: - if ifilename == 0: - eigenvals[Spin.down][iband][ikpoint] = float(line.split()[1]) + self.efermi - p_eigenvals[Spin.down][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - - linenumber += 1 - iband += 1 - - self.kpoints_array = kpoints_array - self.eigenvals = eigenvals - self.p_eigenvals = p_eigenvals - - label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): - if label is not None: - label_dict[label] = kpoints_array[ilabel] - - self.label_dict = label_dict - - def get_bandstructure(self): - """Returns a LobsterBandStructureSymmLine object which can be plotted with a normal BSPlotter.""" - return LobsterBandStructureSymmLine( - kpoints=self.kpoints_array, - eigenvals=self.eigenvals, - lattice=self.lattice, - efermi=self.efermi, - labels_dict=self.label_dict, - structure=self.structure, - projections=self.p_eigenvals, - ) - - -class Bandoverlaps: - """ - Class to read in bandOverlaps.lobster files. These files are not created during every Lobster run. - Attributes: - bandoverlapsdict (dict[Spin, Dict[str, Dict[str, Union[float, np.ndarray]]]]): A dictionary - containing the band overlap data of the form: {spin: {"kpoint as string": {"maxDeviation": - float that describes the max deviation, "matrix": 2D array of the size number of bands - times number of bands including the overlap matrices with}}}. - maxDeviation (list[float]): A list of floats describing the maximal deviation for each problematic kpoint. - """ - - def __init__(self, filename: str = "bandOverlaps.lobster"): - """ - Args: - filename: filename of the "bandOverlaps.lobster" file. - """ - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - spin_numbers = [0, 1] if contents[0].split()[-1] == "0" else [1, 2] - - self._read(contents, spin_numbers) - - def _read(self, contents: list, spin_numbers: list): - """ - Will read in all contents of the file - - Args: - contents: list of strings - spin_numbers: list of spin numbers depending on `Lobster` version. - """ - self.bandoverlapsdict: dict[Any, dict] = {} # Any is spin number 1 or -1 - self.max_deviation = [] - # This has to be done like this because there can be different numbers of problematic k-points per spin - for line in contents: - if f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[0]}" in line: - spin = Spin.up - elif f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[1]}" in line: - spin = Spin.down - elif "k-point" in line: - kpoint = line.split(" ") - kpoint_array = [] - for kpointel in kpoint: - if kpointel not in ["at", "k-point", ""]: - kpoint_array.append(str(kpointel)) - - elif "maxDeviation" in line: - if spin not in self.bandoverlapsdict: - self.bandoverlapsdict[spin] = {} - if " ".join(kpoint_array) not in self.bandoverlapsdict[spin]: - self.bandoverlapsdict[spin][" ".join(kpoint_array)] = {} - maxdev = line.split(" ")[2] - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["maxDeviation"] = float(maxdev) - self.max_deviation.append(float(maxdev)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"] = [] - - else: - overlaps = [] - for el in line.split(" "): - if el not in [""]: - overlaps.append(float(el)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"].append(overlaps) - - def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool: - """ - Will check if the maxDeviation from the ideal bandoverlap is smaller or equal to limit_maxDeviation - - Args: - limit_maxDeviation: limit of the maxDeviation - - Returns: - Boolean that will give you information about the quality of the projection. - """ - return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) - - def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, - ) -> bool: - """ - Will check if the deviation from the ideal bandoverlap of all occupied bands - is smaller or equal to limit_deviation. - - Args: - number_occ_bands_spin_up (int): number of occupied bands of spin up - number_occ_bands_spin_down (int): number of occupied bands of spin down - spin_polarized (bool): If True, then it was a spin polarized calculation - limit_deviation (float): limit of the maxDeviation - - Returns: - Boolean that will give you information about the quality of the projection - """ - for matrix in self.bandoverlapsdict[Spin.up].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if iband1 < number_occ_bands_spin_up and iband2 < number_occ_bands_spin_up: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - - if spin_polarized: - for matrix in self.bandoverlapsdict[Spin.down].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if number_occ_bands_spin_down is not None: - if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - else: - ValueError("number_occ_bands_spin_down has to be specified") - return True - - -class Grosspop: - """ - Class to read in GROSSPOP.lobster files. - - Attributes: - list_dict_grosspop (list[dict[str, str| dict[str, str]]]): List of dictionaries - including all information about the grosspopulations. Each dictionary contains the following keys: - - 'element': The element symbol of the atom. - - 'Mulliken GP': A dictionary of Mulliken gross populations, where the keys are the orbital labels and the - values are the corresponding gross populations as strings. - - 'Loewdin GP': A dictionary of Loewdin gross populations, where the keys are the orbital labels and the - values are the corresponding gross populations as strings. - The 0th entry of the list refers to the first atom in GROSSPOP.lobster and so on. - """ - - def __init__(self, filename: str = "GROSSPOP.lobster"): - """ - Args: - filename: filename of the "GROSSPOP.lobster" file. - """ - # opens file - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - self.list_dict_grosspop = [] - # transfers content of file to list of dict - for line in contents[3:]: - cleanline = [i for i in line.split(" ") if i != ""] - if len(cleanline) == 5: - small_dict = {} - small_dict["element"] = cleanline[1] - small_dict["Mulliken GP"] = {} - small_dict["Loewdin GP"] = {} - small_dict["Mulliken GP"][cleanline[2]] = float(cleanline[3]) - small_dict["Loewdin GP"][cleanline[2]] = float(cleanline[4]) - elif len(cleanline) > 0: - small_dict["Mulliken GP"][cleanline[0]] = float(cleanline[1]) - small_dict["Loewdin GP"][cleanline[0]] = float(cleanline[2]) - if "total" in cleanline[0]: - self.list_dict_grosspop.append(small_dict) - - def get_structure_with_total_grosspop(self, structure_filename: str) -> Structure: - """ - Get a Structure with Mulliken and Loewdin total grosspopulations as site properties - - Args: - structure_filename (str): filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin total grosspopulations as site properties. - """ - struct = Structure.from_file(structure_filename) - site_properties: dict[str, Any] = {} - mullikengp = [] - loewdingp = [] - for grosspop in self.list_dict_grosspop: - mullikengp.append(grosspop["Mulliken GP"]["total"]) - loewdingp.append(grosspop["Loewdin GP"]["total"]) - - site_properties = { - "Total Mulliken GP": mullikengp, - "Total Loewdin GP": loewdingp, - } - return struct.copy(site_properties=site_properties) - - -class Wavefunction: - """ - Class to read in wave function files from Lobster and transfer them into an object of the type VolumetricData. - - Attributes: - grid (tuple[int, int, int]): Grid for the wave function [Nx+1,Ny+1,Nz+1]. - points (list[Tuple[float, float, float]]): List of points. - real (list[float]): List of real part of wave function. - imaginary (list[float]): List of imaginary part of wave function. - distance (list[float]): List of distance to first point in wave function file. - """ - - def __init__(self, filename, structure): - """ - Args: - filename: filename of wavecar file from Lobster - structure: Structure object (e.g., created by Structure.from_file("")). - """ - self.filename = filename - self.structure = structure - self.grid, self.points, self.real, self.imaginary, self.distance = Wavefunction._parse_file(filename) - - @staticmethod - def _parse_file(filename): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - points = [] - distance = [] - real = [] - imaginary = [] - splitline = contents[0].split() - grid = [int(splitline[7]), int(splitline[8]), int(splitline[9])] - for line in contents[1:]: - splitline = line.split() - if len(splitline) >= 6: - points.append([float(splitline[0]), float(splitline[1]), float(splitline[2])]) - distance.append(float(splitline[3])) - real.append(float(splitline[4])) - imaginary.append(float(splitline[5])) - - if not len(real) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - if not len(imaginary) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - return grid, points, real, imaginary, distance - - def set_volumetric_data(self, grid, structure): - """ - Will create the VolumetricData Objects. - - Args: - grid: grid on which wavefunction was calculated, e.g. [1,2,2] - structure: Structure object - """ - Nx = grid[0] - 1 - Ny = grid[1] - 1 - Nz = grid[2] - 1 - a = structure.lattice.matrix[0] - b = structure.lattice.matrix[1] - c = structure.lattice.matrix[2] - new_x = [] - new_y = [] - new_z = [] - new_real = [] - new_imaginary = [] - new_density = [] - - runner = 0 - for x in range(Nx + 1): - for y in range(Ny + 1): - for z in range(Nz + 1): - x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] - y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] - z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - - if x != Nx and y != Ny and z != Nz: - if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) - ): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x.append(x_here) - new_y.append(y_here) - new_z.append(z_here) - - new_real.append(self.real[runner]) - new_imaginary.append(self.imaginary[runner]) - new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) - - runner += 1 - - self.final_real = np.reshape(new_real, [Nx, Ny, Nz]) - self.final_imaginary = np.reshape(new_imaginary, [Nx, Ny, Nz]) - self.final_density = np.reshape(new_density, [Nx, Ny, Nz]) - - self.volumetricdata_real = VolumetricData(structure, {"total": self.final_real}) - self.volumetricdata_imaginary = VolumetricData(structure, {"total": self.final_imaginary}) - self.volumetricdata_density = VolumetricData(structure, {"total": self.final_density}) - - def get_volumetricdata_real(self): - """ - Will return a VolumetricData object including the real part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_real"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_real - - def get_volumetricdata_imaginary(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_imaginary"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_imaginary - - def get_volumetricdata_density(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_density"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_density - - def write_file(self, filename="WAVECAR.vasp", part="real"): - """ - Will save the wavefunction in a file format that can be read by VESTA - This will only work if the wavefunction from lobster was constructed with: - "printLCAORealSpaceWavefunction kpoint 1 coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 2 3 4 - 5 6 " - or similar (the whole unit cell has to be covered!). - - Args: - filename: Filename for the output, e.g., WAVECAR.vasp - part: which part of the wavefunction will be saved ("real" or "imaginary") - """ - if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") - ): - self.set_volumetric_data(self.grid, self.structure) - if part == "real": - self.volumetricdata_real.write_file(filename) - elif part == "imaginary": - self.volumetricdata_imaginary.write_file(filename) - elif part == "density": - self.volumetricdata_density.write_file(filename) - else: - raise ValueError('part can be only "real" or "imaginary" or "density"') - - -# madleung and sitepotential classes -class MadelungEnergies: - """ - Class to read MadelungEnergies.lobster files generated by LOBSTER. - - Attributes: - madelungenergies_Mulliken (float): Float that gives the Madelung energy based on the Mulliken approach. - madelungenergies_Loewdin (float): Float that gives the Madelung energy based on the Loewdin approach. - ewald_splitting (float): Ewald splitting parameter to compute SitePotentials. - """ - - def __init__(self, filename: str = "MadelungEnergies.lobster"): - """ - Args: - filename: filename of the "MadelungEnergies.lobster" file. - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[5] - if len(data) == 0: - raise OSError("MadelungEnergies file contains no data.") - line = data.split() - self.ewald_splitting = float(line[0]) - self.madelungenergies_Mulliken = float(line[1]) - self.madelungenergies_Loewdin = float(line[2]) - - -class SitePotential: - """ - Class to read SitePotentials.lobster files generated by LOBSTER. - - Attributes: - atomlist (list[str]): List of atoms in SitePotentials.lobster. - types (list[str]): List of types of atoms in SitePotentials.lobster. - num_atoms (int): Number of atoms in SitePotentials.lobster. - sitepotentials_Mulliken (list[float]): List of Mulliken potentials of sites in SitePotentials.lobster. - sitepotentials_Loewdin (list[float]): List of Loewdin potentials of sites in SitePotentials.lobster. - madelung_Mulliken (float): Float that gives the Madelung energy based on the Mulliken approach. - madelung_Loewdin (float): Float that gives the Madelung energy based on the Loewdin approach. - ewald_splitting (float): Ewald Splitting parameter to compute SitePotentials. - """ - - def __init__(self, filename: str = "SitePotentials.lobster"): - """ - Args: - filename: filename for the SitePotentials file, typically "SitePotentials.lobster". - """ - # site_potentials - with zopen(filename, "rt") as f: - data = f.read().split("\n") - if len(data) == 0: - raise OSError("SitePotentials file contains no data.") - - self.ewald_splitting = float(data[0].split()[9]) - - data = data[5:-1] - self.num_atoms = len(data) - 2 - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.sitepotentials_Mulliken: list[float] = [] - self.sitepotentials_Loewdin: list[float] = [] - for atom in range(self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + str(line[0])) - self.types.append(line[1]) - self.sitepotentials_Mulliken.append(float(line[2])) - self.sitepotentials_Loewdin.append(float(line[3])) - - self.madelungenergies_Mulliken = float(data[self.num_atoms + 1].split()[3]) - self.madelungenergies_Loewdin = float(data[self.num_atoms + 1].split()[4]) - - def get_structure_with_site_potentials(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - - Args: - structure_filename: filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin charges as site properties. - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.sitepotentials_Mulliken - Loewdin = self.sitepotentials_Loewdin - site_properties = { - "Mulliken Site Potentials (eV)": Mulliken, - "Loewdin Site Potentials (eV)": Loewdin, - } - return struct.copy(site_properties=site_properties) - - -def get_orb_from_str(orbs): - """ - - Args: - orbs: list of two str, e.g. ["2p_x", "3s"]. - - Returns: - list of tw Orbital objects - - """ - # TODO: also useful for plotting of dos - orb_labs = [ - "s", - "p_y", - "p_z", - "p_x", - "d_xy", - "d_yz", - "d_z^2", - "d_xz", - "d_x^2-y^2", - "f_y(3x^2-y^2)", - "f_xyz", - "f_yz^2", - "f_z^3", - "f_xz^2", - "f_z(x^2-y^2)", - "f_x(x^2-3y^2)", - ] - orbitals = [(int(orb[0]), Orbital(orb_labs.index(orb[1:]))) for orb in orbs] - orb_label = f"{orbitals[0][0]}{orbitals[0][1].name}-{orbitals[1][0]}{orbitals[1][1].name}" # type: ignore - return orb_label, orbitals diff --git a/pymatgen/io/lobster/outputs.py.orig b/pymatgen/io/lobster/outputs.py.orig deleted file mode 100644 index eb60bc17d89..00000000000 --- a/pymatgen/io/lobster/outputs.py.orig +++ /dev/null @@ -1,1756 +0,0 @@ -""" -Module for reading Lobster output files. For more information -on LOBSTER see www.cohp.de. -If you use this module, please cite: -J. George, G. Petretto, A. Naik, M. Esters, A. J. Jackson, R. Nelson, R. Dronskowski, G.-M. Rignanese, G. Hautier, -"Automated Bonding Analysis with Crystal Orbital Hamilton Populations", -ChemPlusChem 2022, e202200123, -DOI: 10.1002/cplu.202200123. -""" - -from __future__ import annotations - -import collections -import fnmatch -import os -import re -import warnings -from collections import defaultdict -from typing import TYPE_CHECKING, Any - -import numpy as np -from monty.io import zopen - -from pymatgen.core.structure import Structure -from pymatgen.electronic_structure.bandstructure import LobsterBandStructureSymmLine -from pymatgen.electronic_structure.core import Orbital, Spin -from pymatgen.electronic_structure.dos import Dos, LobsterCompleteDos -from pymatgen.io.vasp.inputs import Kpoints -from pymatgen.io.vasp.outputs import Vasprun, VolumetricData -from pymatgen.util.due import Doi, due - -if TYPE_CHECKING: - from pymatgen.core.structure import IStructure - -__author__ = "Janine George, Marco Esters" -__copyright__ = "Copyright 2017, The Materials Project" -__version__ = "0.2" -__maintainer__ = "Janine George " -__email__ = "janinegeorge.ulfen@gmail.com" -__date__ = "Dec 13, 2017" - -MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) - -due.cite( - Doi("10.1002/cplu.202200123"), - description="Automated Bonding Analysis with Crystal Orbital Hamilton Populations", -) - -<<<<<<< HEAD -class Cohpcar: - """ - Class to read COHPCAR/COOPCAR/COBICAR files generated by LOBSTER. - - .. attribute: cohp_data - - Dict that contains the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have - a "length" key. - - .. attribute: efermi - - The Fermi energy in eV. - - .. attribute: energies - - Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - - .. attribute: is_spin_polarized - - Boolean to indicate if the calculation is spin polarized. - - .. attribute: orb_cohp - - orb_cohp[label] = {bond_data["orb_label"]: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}} -======= ->>>>>>> master - -class Cohpcar: - """Class to read COHPCAR/COOPCAR files generated by LOBSTER. - - Attributes: - cohp_data (dict[str, Dict[str, Any]]): A dictionary containing the COHP data of the form: - {bond: {"COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "length": bond length, - "sites": sites corresponding to the bond} - Also contains an entry for the average, which does not have a "length" key. - efermi (float): The Fermi energy in eV. - energies (Sequence[float]): Sequence of energies in eV. Note that LOBSTER shifts the energies - so that the Fermi energy is at zero. - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - orb_cohp (dict[str, Dict[str, Dict[str, Any]]]): A dictionary containing the orbital-resolved COHPs of the form: - orb_cohp[label] = {bond_data["orb_label"]: { - "COHP": {Spin.up: cohps, Spin.down:cohps}, - "ICOHP": {Spin.up: icohps, Spin.down: icohps}, - "orbitals": orbitals, - "length": bond lengths, - "sites": sites corresponding to the bond}, - } - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of COHPs or COOPs. - Default is False for COHPs. - are_cobis: Determines if the file is a list of COHPs or COBIs. - Default is False for COHPs. - - filename: Name of the COHPCAR file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "COOPCAR.lobster" - elif are_cobis: - filename = "COBICAR.lobster" - else: - filename = "COHPCAR.lobster" - - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - # The parameters line is the second line in a COHPCAR file. It - # contains all parameters that are needed to map the file. - parameters = contents[1].split() - # Subtract 1 to skip the average - num_bonds = int(parameters[0]) - 1 - self.efermi = float(parameters[-1]) - self.is_spin_polarized = int(parameters[1]) == 2 - spins = [Spin.up, Spin.down] if int(parameters[1]) == 2 else [Spin.up] - - # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] - cohp_data: dict[str, dict[str, Any]] = { - "average": { - "COHP": {spin: data[1 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - "ICOHP": {spin: data[2 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, - } - } - - orb_cohp: dict[str, Any] = {} - # present for Lobster versions older than Lobster 2.2.0 - very_old = False - # the labeling had to be changed: there are more than one COHP for each atom combination - # this is done to make the labeling consistent with ICOHPLIST.lobster - bond_num = 0 - for bond in range(num_bonds): - bond_data = self._get_bond_data(contents[3 + bond]) - - label = str(bond_num) - - orbs = bond_data["orbitals"] - cohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 3] for s, spin in enumerate(spins)} - - icohp = {spin: data[2 * (bond + s * (num_bonds + 1)) + 4] for s, spin in enumerate(spins)} - if orbs is None: - bond_num = bond_num + 1 - label = str(bond_num) - cohp_data[label] = { - "COHP": cohp, - "ICOHP": icohp, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - ) - else: - # present for Lobster versions older than Lobster 2.2.0 - if bond_num == 0: - very_old = True - if very_old: - bond_num += 1 - label = str(bond_num) - - orb_cohp[label] = { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - } - - # present for lobster older than 2.2.0 - if very_old: - for bond_str in orb_cohp: - cohp_data[bond_str] = { - "COHP": None, - "ICOHP": None, - "length": bond_data["length"], - "sites": bond_data["sites"], - } - - self.orb_res_cohp = orb_cohp or None - self.cohp_data = cohp_data - - @staticmethod - def _get_bond_data(line: str) -> dict: - """ - Subroutine to extract bond label, site indices, and length from - a LOBSTER header line. The site indices are zero-based, so they - can be easily used with a Structure object. - - Example header line: No.4:Fe1->Fe9(2.4524893531900283) - Example header line for orbtial-resolved COHP: - No.1:Fe1[3p_x]->Fe2[3d_x^2-y^2](2.456180552772262) - - Args: - line: line in the COHPCAR header describing the bond. - - Returns: - Dict with the bond label, the bond length, a tuple of the site - indices, a tuple containing the orbitals (if orbital-resolved), - and a label for the orbitals (if orbital-resolved). - """ - line_new = line.rsplit("(", 1) - length = float(line_new[-1][:-1]) - - sites = line_new[0].replace("->", ":").split(":")[1:3] - site_indices = tuple(int(re.split(r"\D+", site)[1]) - 1 for site in sites) - - if "[" in sites[0]: - orbs = [re.findall(r"\[(.*)\]", site)[0] for site in sites] - orb_label, orbitals = get_orb_from_str(orbs) - - else: - orbitals = orb_label = None - - return { - "length": length, - "sites": site_indices, - "orbitals": orbitals, - "orb_label": orb_label, - } - - -class Icohplist: - """ - Class to read ICOHPLIST/ICOOPLIST files generated by LOBSTER. - -<<<<<<< HEAD - .. attribute: are_coops - Boolean to indicate if the populations are COOPs, COHPs or COBIs. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Icohplist - Dict containing the listfile data of the form: - {bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...}} - - .. attribute: IcohpCollection - IcohpCollection Object - -======= - Attributes: - are_coops (bool): Indicates whether the object is consisting of COOPs. - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - Icohplist (dict[str, Dict[str, Union[float, int, Dict[Spin, float]]]]): Dict containing the - listfile data of the form: { - bond: "length": bond length, - "number_of_bonds": number of bonds - "icohp": {Spin.up: ICOHP(Ef) spin up, Spin.down: ...} - } - IcohpCollection (IcohpCollection): IcohpCollection Object. ->>>>>>> master - """ - - def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: str | None = None): - """ - Args: - are_coops: Determines if the file is a list of ICOOPs. - Defaults to False for ICOHPs. - are_cobis: Determines if the file is a list of ICOBIs. - Defaults to False for ICOHPs. - filename: Name of the ICOHPLIST file. If it is None, the default - file name will be chosen, depending on the value of are_coops. - """ - if are_coops and are_cobis: - raise ValueError("You cannot have info about COOPs and COBIs in the same file.") - self.are_coops = are_coops - self.are_cobis = are_cobis - if filename is None: - if are_coops: - filename = "ICOOPLIST.lobster" - elif are_cobis: - filename = "ICOBILIST.lobster" - else: - filename = "ICOHPLIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("ICOHPLIST file contains no data.") - - # Which Lobster version? - if len(data[0].split()) == 8: - version = "3.1.1" - elif len(data[0].split()) == 6: - version = "2.2.1" - warnings.warn("Please consider using the new Lobster version. See www.cohp.de.") - else: - raise ValueError - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = "distance" in data[len(data) // 2] - - # check if orbitalwise ICOHPLIST - # include case when there is only one ICOHP!!! - self.orbitalwise = len(data) > 2 and "_" in data[1].split()[1] - - if self.orbitalwise: - data_without_orbitals = [] - data_orbitals = [] - for line in data: - if "_" not in line.split()[1]: - data_without_orbitals.append(line) - else: - data_orbitals.append(line) - - else: - data_without_orbitals = data - - if "distance" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("ICOHPLIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - list_labels = [] - list_atom1 = [] - list_atom2 = [] - list_length = [] - list_translation = [] - list_num = [] - list_icohp = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - icohp = {} - if version == "2.2.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - icohp[Spin.up] = float(line[4]) - num = int(line[5]) - translation = [0, 0, 0] - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[4]) - - elif version == "3.1.1": - label = f"{line[0]}" - atom1 = str(line[1]) - atom2 = str(line[2]) - length = float(line[3]) - translation = [int(line[4]), int(line[5]), int(line[6])] - icohp[Spin.up] = float(line[7]) - num = 1 - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[7]) - - list_labels.append(label) - list_atom1.append(atom1) - list_atom2.append(atom2) - list_length.append(length) - list_translation.append(translation) - list_num.append(num) - list_icohp.append(icohp) - - list_orb_icohp: list[dict] | None = None - if self.orbitalwise: - list_orb_icohp = [] - num_orbs = len(data_orbitals) // 2 if self.is_spin_polarized else len(data_orbitals) - - for i_data_orb in range(num_orbs): - data_orb = data_orbitals[i_data_orb] - icohp = {} - line = data_orb.split() - label = f"{line[0]}" - orbs = re.findall(r"_(.*?)(?=\s)", data_orb) - orb_label, orbitals = get_orb_from_str(orbs) - icohp[Spin.up] = float(line[7]) - - if self.is_spin_polarized: - icohp[Spin.down] = float(data_orbitals[num_orbs + i_data_orb].split()[7]) - - if len(list_orb_icohp) < int(label): - list_orb_icohp.append({orb_label: {"icohp": icohp, "orbitals": orbitals}}) - else: - list_orb_icohp[int(label) - 1][orb_label] = {"icohp": icohp, "orbitals": orbitals} - - # to avoid circular dependencies - from pymatgen.electronic_structure.cohp import IcohpCollection - - self._icohpcollection = IcohpCollection( - are_coops=are_coops, - are_cobis=are_cobis, - list_labels=list_labels, - list_atom1=list_atom1, - list_atom2=list_atom2, - list_length=list_length, - list_translation=list_translation, - list_num=list_num, - list_icohp=list_icohp, - is_spin_polarized=self.is_spin_polarized, - list_orb_icohp=list_orb_icohp, - ) - - @property - def icohplist(self) -> dict[Any, dict[str, Any]]: - """Returns: icohplist compatible with older version of this class.""" - icohplist_new = {} - for key, value in self._icohpcollection._icohplist.items(): - icohplist_new[key] = { - "length": value._length, - "number_of_bonds": value._num, - "icohp": value._icohp, - "translation": value._translation, - "orbitals": value._orbitals, - } - return icohplist_new - - @property - def icohpcollection(self): - """Returns: IcohpCollection object.""" - return self._icohpcollection - - -class Ncicobilist: - """ - Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. - - .. attribute: is_spin_polarized - Boolean to indicate if the calculation is spin polarized. - - .. attribute: Ncicobilist - Dict containing the listfile data of the form: - {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, - "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, - "interaction_type": type of the multi-center interaction - - """ - - def __init__(self, filename: str | None = None): - """ - Args: - filename: Name of the NcICOBILIST file. - """ - - if filename is None: - warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") - filename = "NcICOBILIST.lobster" - - # LOBSTER list files have an extra trailing blank line - # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] - if len(data) == 0: - raise OSError("NcICOBILIST file contains no data.") - - # If the calculation is spin polarized, the line in the middle - # of the file will be another header line. - if "spin" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise stuff - self.is_spin_polarized = True - else: - self.is_spin_polarized = False - - # check if orbitalwise NcICOBILIST - # include case when there is only one NcICOBI - for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed - if len(data) > 2 and "s]" in str(entry.split()[3:]): - self.orbitalwise = True - warnings.warn( - "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " - + "information is not read!" - ) - break # condition has only to be met once - else: - self.orbitalwise = False - - if self.orbitalwise: - data_without_orbitals = [] - for line in data: - if "_" not in str(line.split()[3:]) and "s]" not in str(line.split()[3:]): - data_without_orbitals.append(line) - else: - data_without_orbitals = data - - if "spin" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: - raise OSError("NcICOBILIST file contains no data.") - else: - num_bonds = len(data_without_orbitals) - - self.list_labels = [] - self.list_numofatoms = [] - self.list_ncicobi = [] - self.list_interactiontype = [] - self.list_num = [] - - for bond in range(num_bonds): - line = data_without_orbitals[bond].split() - ncicobi = {} - - label = f"{line[0]}" - numofatoms = str(line[1]) - ncicobi[Spin.up] = float(line[2]) - interactiontype = str(line[3:]).replace("'", "").replace(" ", "") - num = int(1) - - if self.is_spin_polarized: - ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) - - self.list_labels.append(label) - self.list_numofatoms.append(numofatoms) - self.list_ncicobi.append(ncicobi) - self.list_interactiontype.append(interactiontype) - self.list_num.append(num) - - # TODO: add functions to get orbital resolved NcICOBIs - - @property - def ncicobilist(self) -> dict[Any, dict[str, Any]]: - """ - Returns: ncicobilist. - """ - ncicobilist = {} - for key, _entry in enumerate(self.list_labels): - ncicobilist[str(key + 1)] = { - "number_of_atoms": int(self.list_numofatoms[key]), - "ncicobi": self.list_ncicobi[key], - "interaction_type": self.list_interactiontype[key], - } - - return ncicobilist - - -class Doscar: - """ - Class to deal with Lobster's projected DOS and local projected DOS. - The beforehand quantum-chemical calculation was performed with VASP. - - Attributes: - completedos (LobsterCompleteDos): LobsterCompleteDos Object. - pdos (list): List of Dict including numpy arrays with pdos. Access as - pdos[atomindex]['orbitalstring']['Spin.up/Spin.down']. - tdos (Dos): Dos Object of the total density of states. - energies (numpy.ndarray): Numpy array of the energies at which the DOS was calculated - (in eV, relative to Efermi). - tdensities (dict): tdensities[Spin.up]: numpy array of the total density of states for - the Spin.up contribution at each of the energies. tdensities[Spin.down]: numpy array - of the total density of states for the Spin.down contribution at each of the energies. - If is_spin_polarized=False, tdensities[Spin.up]: numpy array of the total density of states. - itdensities (dict): itdensities[Spin.up]: numpy array of the total density of states for - the Spin.up contribution at each of the energies. itdensities[Spin.down]: numpy array - of the total density of states for the Spin.down contribution at each of the energies. - If is_spin_polarized=False, itdensities[Spin.up]: numpy array of the total density of states. - is_spin_polarized (bool): Boolean. Tells if the system is spin polarized. - """ - - def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str | None = "POSCAR", - structure: IStructure | Structure | None = None, - ): - """ - Args: - doscar: DOSCAR filename, typically "DOSCAR.lobster" - structure_file: for vasp, this is typically "POSCAR" - structure: instead of a structure file, the structure can be given - directly. structure_file will be preferred. - """ - self._doscar = doscar - - self._final_structure = Structure.from_file(structure_file) if structure_file is not None else structure - - self._parse_doscar() - - def _parse_doscar(self): - doscar = self._doscar - - tdensities = {} - itdensities = {} - with zopen(doscar, "rt") as f: - natoms = int(f.readline().split()[0]) - efermi = float([f.readline() for nn in range(4)][3].split()[17]) - dos = [] - orbitals = [] - for _atom in range(natoms + 1): - line = f.readline() - ndos = int(line.split()[2]) - orbitals.append(line.split(";")[-1].split()) - line = f.readline().split() - cdos = np.zeros((ndos, len(line))) - cdos[0] = np.array(line) - for nd in range(1, ndos): - line = f.readline().split() - cdos[nd] = np.array(line) - dos.append(cdos) - doshere = np.array(dos[0]) - if len(doshere[0, :]) == 5: - self._is_spin_polarized = True - elif len(doshere[0, :]) == 3: - self._is_spin_polarized = False - else: - raise ValueError("There is something wrong with the DOSCAR. Can't extract spin polarization.") - energies = doshere[:, 0] - if not self._is_spin_polarized: - tdensities[Spin.up] = doshere[:, 1] - itdensities[Spin.up] = doshere[:, 2] - pdoss = [] - spin = Spin.up - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - orbnumber = orbnumber + 1 - pdoss.append(pdos) - else: - tdensities[Spin.up] = doshere[:, 1] - tdensities[Spin.down] = doshere[:, 2] - itdensities[Spin.up] = doshere[:, 3] - itdensities[Spin.down] = doshere[:, 4] - pdoss = [] - for atom in range(natoms): - pdos = defaultdict(dict) - data = dos[atom + 1] - _, ncol = data.shape - orbnumber = 0 - for j in range(1, ncol): - spin = Spin.down if j % 2 == 0 else Spin.up - orb = orbitals[atom + 1][orbnumber] - pdos[orb][spin] = data[:, j] - if j % 2 == 0: - orbnumber = orbnumber + 1 - pdoss.append(pdos) - - self._efermi = efermi - self._pdos = pdoss - self._tdos = Dos(efermi, energies, tdensities) - self._energies = energies - self._tdensities = tdensities - self._itdensities = itdensities - final_struct = self._final_structure - - pdossneu = {final_struct[i]: pdos for i, pdos in enumerate(self._pdos)} - - self._completedos = LobsterCompleteDos(final_struct, self._tdos, pdossneu) - - @property - def completedos(self) -> LobsterCompleteDos: - """LobsterCompleteDos""" - return self._completedos - - @property - def pdos(self) -> list: - """Projected DOS""" - return self._pdos - - @property - def tdos(self) -> Dos: - """Total DOS""" - return self._tdos - - @property - def energies(self) -> np.ndarray: - """Energies""" - return self._energies - - @property - def tdensities(self) -> np.ndarray: - """total densities as a np.ndarray""" - return self._tdensities - - @property - def itdensities(self) -> np.ndarray: - """integrated total densities as a np.ndarray""" - return self._itdensities - - @property - def is_spin_polarized(self) -> bool: - """Whether run is spin polarized.""" - return self._is_spin_polarized - - -class Charge: - """ - Class to read CHARGE files generated by LOBSTER. - - Attributes: - atomlist (list[str]): List of atoms in CHARGE.lobster. - types (list[str]): List of types of atoms in CHARGE.lobster. - Mulliken (list[float]): List of Mulliken charges of atoms in CHARGE.lobster. - Loewdin (list[float]): List of Loewdin charges of atoms in CHARGE.Loewdin. - num_atoms (int): Number of atoms in CHARGE.lobster. - """ - - def __init__(self, filename: str = "CHARGE.lobster"): - """ - Args: - filename: filename for the CHARGE file, typically "CHARGE.lobster". - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[3:-3] - if len(data) == 0: - raise OSError("CHARGES file contains no data.") - - self.num_atoms = len(data) - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.Mulliken: list[float] = [] - self.Loewdin: list[float] = [] - for atom in range(self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + line[0]) - self.types.append(line[1]) - self.Mulliken.append(float(line[2])) - self.Loewdin.append(float(line[3])) - - def get_structure_with_charges(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - - Args: - structure_filename: filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin charges as site properties. - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.Mulliken - Loewdin = self.Loewdin - site_properties = {"Mulliken Charges": Mulliken, "Loewdin Charges": Loewdin} - return struct.copy(site_properties=site_properties) - - -class Lobsterout: - """ - Class to read in the lobsterout and evaluate the spilling, save the basis, save warnings, save infos. - - Attributes: - basis_functions (list[str]): List of basis functions that were used in lobster run as strings. - basis_type (list[str]): List of basis type that were used in lobster run as strings. - charge_spilling (list[float]): List of charge spilling (first entry: result for spin 1, - second entry: result for spin 2 or not present). - dft_program (str): String representing the DFT program used for the calculation of the wave function. - elements (list[str]): List of strings of elements that were present in lobster calculation. - has_charge (bool): Whether CHARGE.lobster is present. - has_cohpcar (bool): Whether COHPCAR.lobster and ICOHPLIST.lobster are present. - has_madelung (bool): Whether SitePotentials.lobster and MadelungEnergies.lobster are present. - has_coopcar (bool): Whether COOPCAR.lobster and ICOOPLIST.lobster are present. - has_cobicar (bool): Whether COBICAR.lobster and ICOBILIST.lobster are present. - has_doscar (bool): Whether DOSCAR.lobster is present. - has_doscar_lso (bool): Whether DOSCAR.LSO.lobster is present. - has_projection (bool): Whether projectionData.lobster is present. - has_bandoverlaps (bool): Whether bandOverlaps.lobster is present. - has_density_of_energies (bool): Whether DensityOfEnergy.lobster is present. - has_fatbands (bool): Whether fatband calculation was performed. - has_grosspopulation (bool): Whether GROSSPOP.lobster is present. - info_lines (str): String with additional infos on the run. - info_orthonormalization (str): String with infos on orthonormalization. - is_restart_from_projection (bool): Boolean that indicates that calculation was restarted - from existing projection file. - lobster_version (str): String that indicates Lobster version. - number_of_spins (int): Integer indicating the number of spins. - number_of_threads (int): Integer that indicates how many threads were used. - timing (dict[str, float]): Dictionary with infos on timing. - total_spilling (list[float]): List of values indicating the total spilling for spin - channel 1 (and spin channel 2). - warning_lines (str): String with all warnings. - """ - - # TODO: add tests for skipping COBI and madelung - # TODO: add tests for including COBI and madelung - def __init__(self, filename="lobsterout"): - """ - Args: - filename: filename of lobsterout. - """ - # read in file - with zopen(filename, "rt") as f: - data = f.read().split("\n") # [3:-3] - if len(data) == 0: - raise OSError("lobsterout does not contain any data") - - # check if Lobster starts from a projection - self.is_restart_from_projection = "loading projection from projectionData.lobster..." in data - - self.lobster_version = self._get_lobster_version(data=data) - - self.number_of_threads = int(self._get_threads(data=data)) - self.dft_program = self._get_dft_program(data=data) - - self.number_of_spins = self._get_number_of_spins(data=data) - chargespilling, totalspilling = self._get_spillings(data=data, number_of_spins=self.number_of_spins) - self.charge_spilling = chargespilling - self.total_spilling = totalspilling - - elements, basistype, basisfunctions = self._get_elements_basistype_basisfunctions(data=data) - self.elements = elements - self.basis_type = basistype - self.basis_functions = basisfunctions - - wall_time, user_time, sys_time = self._get_timing(data=data) - timing = {} - timing["wall_time"] = wall_time - timing["user_time"] = user_time - timing["sys_time"] = sys_time - self.timing = timing - - warninglines = self._get_all_warning_lines(data=data) - self.warning_lines = warninglines - - orthowarning = self._get_warning_orthonormalization(data=data) - self.info_orthonormalization = orthowarning - - infos = self._get_all_info_lines(data=data) - self.info_lines = infos - - self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data - self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data - ) - self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data - ) - self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data - ) - self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data - ) - - self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data - self.has_projection = "saving projection to projectionData.lobster..." in data - self.has_bandoverlaps = "WARNING: I dumped the band overlap matrices to the file bandOverlaps.lobster." in data - self.has_fatbands = self._has_fatband(data=data) - self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data - self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data - self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data - ) - - def get_doc(self): - """Returns: LobsterDict with all the information stored in lobsterout.""" - LobsterDict = {} - # check if Lobster starts from a projection - LobsterDict["restart_from_projection"] = self.is_restart_from_projection - LobsterDict["lobster_version"] = self.lobster_version - LobsterDict["threads"] = self.number_of_threads - LobsterDict["dft_program"] = self.dft_program - - LobsterDict["charge_spilling"] = self.charge_spilling - LobsterDict["total_spilling"] = self.total_spilling - - LobsterDict["elements"] = self.elements - LobsterDict["basis_type"] = self.basis_type - LobsterDict["basis_functions"] = self.basis_functions - - LobsterDict["timing"] = self.timing - - LobsterDict["warning_lines"] = self.warning_lines - - LobsterDict["info_orthonormalization"] = self.info_orthonormalization - - LobsterDict["info_lines"] = self.info_lines - - LobsterDict["has_doscar"] = self.has_doscar - LobsterDict["has_doscar_lso"] = self.has_doscar_lso - LobsterDict["has_cohpcar"] = self.has_cohpcar - LobsterDict["has_coopcar"] = self.has_coopcar - LobsterDict["has_cobicar"] = self.has_cobicar - LobsterDict["has_charge"] = self.has_charge - LobsterDict["has_madelung"] = self.has_madelung - LobsterDict["has_projection"] = self.has_projection - LobsterDict["has_bandoverlaps"] = self.has_bandoverlaps - LobsterDict["has_fatbands"] = self.has_fatbands - LobsterDict["has_grosspopulation"] = self.has_grosspopulation - LobsterDict["has_density_of_energies"] = self.has_density_of_energies - - return LobsterDict - - @staticmethod - def _get_lobster_version(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[0] == "LOBSTER": - return splitrow[1] - raise RuntimeError("Version not found.") - - @staticmethod - def _has_fatband(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 1 and splitrow[1] == "FatBand": - return True - return False - - @staticmethod - def _get_dft_program(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 4 and splitrow[3] == "program...": - return splitrow[4] - return None - - @staticmethod - def _get_number_of_spins(data): - if "spillings for spin channel 2" in data: - return 2 - return 1 - - @staticmethod - def _get_threads(data): - for row in data: - splitrow = row.split() - if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): - return splitrow[10] - raise ValueError("Threads not found.") - - @staticmethod - def _get_spillings(data, number_of_spins): - charge_spilling = [] - total_spilling = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 2 and splitrow[2] == "spilling:": - if splitrow[1] == "charge": - charge_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - if splitrow[1] == "total": - total_spilling.append(np.float_(splitrow[3].replace("%", "")) / 100.0) - - if len(charge_spilling) == number_of_spins and len(total_spilling) == number_of_spins: - break - - return charge_spilling, total_spilling - - @staticmethod - def _get_elements_basistype_basisfunctions(data): - begin = False - end = False - elements = [] - basistype = [] - basisfunctions = [] - for row in data: - if begin and not end: - splitrow = row.split() - if splitrow[0] not in [ - "INFO:", - "WARNING:", - "setting", - "calculating", - "post-processing", - "saving", - "spillings", - "writing", - ]: - elements.append(splitrow[0]) - basistype.append(splitrow[1].replace("(", "").replace(")", "")) - # last sign is a '' - basisfunctions.append(splitrow[2:]) - else: - end = True - if "setting up local basis functions..." in row: - begin = True - return elements, basistype, basisfunctions - - @staticmethod - def _get_timing(data): - # will give back wall, user and sys time - begin = False - # end=False - # time=[] - - for row in data: - splitrow = row.split() - if "finished" in splitrow: - begin = True - if begin: - if "wall" in splitrow: - wall_time = splitrow[2:10] - if "user" in splitrow: - user_time = splitrow[0:8] - if "sys" in splitrow: - sys_time = splitrow[0:8] - - wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} - user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} - sys_time_dict = {"h": sys_time[0], "min": sys_time[2], "s": sys_time[4], "ms": sys_time[6]} - - return wall_time_dict, user_time_dict, sys_time_dict - - @staticmethod - def _get_warning_orthonormalization(data): - orthowarning = [] - for row in data: - splitrow = row.split() - if "orthonormalized" in splitrow: - orthowarning.append(" ".join(splitrow[1:])) - return orthowarning - - @staticmethod - def _get_all_warning_lines(data): - ws = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "WARNING:": - ws.append(" ".join(splitrow[1:])) - return ws - - @staticmethod - def _get_all_info_lines(data): - infos = [] - for row in data: - splitrow = row.split() - if len(splitrow) > 0 and splitrow[0] == "INFO:": - infos.append(" ".join(splitrow[1:])) - return infos - - -class Fatband: - """ - Reads in FATBAND_x_y.lobster files. - - Attributes: - efermi (float): Fermi energy read in from vasprun.xml. - eigenvals (dict[Spin, np.ndarray]): Eigenvalues as a dictionary of numpy arrays of shape (nbands, nkpoints). - The first index of the array refers to the band and the second to the index of the kpoint. - The kpoints are ordered according to the order of the kpoints_array attribute. - If the band structure is not spin polarized, we only store one data set under Spin.up. - is_spin_polarized (bool): Boolean that tells you whether this was a spin-polarized calculation. - kpoints_array (list[np.ndarray]): List of kpoints as numpy arrays, in frac_coords of the given - lattice by default. - label_dict (dict[str, Union[str, np.ndarray]]): Dictionary that links a kpoint (in frac coords or Cartesian - coordinates depending on the coords attribute) to a label. - lattice (Lattice): Lattice object of reciprocal lattice as read in from vasprun.xml. - nbands (int): Number of bands used in the calculation. - p_eigenvals (dict[Spin, np.ndarray]): Dictionary of orbital projections as {spin: array of dict}. - The indices of the array are [band_index, kpoint_index]. - The dict is then built the following way: {"string of element": "string of orbital as read in - from FATBAND file"}. If the band structure is not spin polarized, we only store one data set under Spin.up. - structure (Structure): Structure read in from vasprun.xml. - """ - - def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): - """ - Args: - filenames (list or string): can be a list of file names or a path to a folder from which all - "FATBAND_*" files will be read - vasprun: corresponding vasprun file - Kpointsfile: KPOINTS file for bandstructure calculation, typically "KPOINTS". - """ - warnings.warn("Make sure all relevant FATBAND files were generated and read in!") - warnings.warn("Use Lobster 3.2.0 or newer for fatband calculations!") - - vasp_run = Vasprun( - filename=vasprun, - ionic_step_skip=None, - ionic_step_offset=0, - parse_dos=True, - parse_eigen=False, - parse_projected_eigen=False, - parse_potcar_file=False, - occu_tol=1e-8, - exception_on_bad_xml=True, - ) - self.structure = vasp_run.final_structure - self.lattice = self.structure.lattice.reciprocal_lattice - self.efermi = vasp_run.efermi - kpoints_object = Kpoints.from_file(Kpointsfile) - - atomtype = [] - atomnames = [] - orbital_names = [] - - if not isinstance(filenames, list) or filenames is None: - filenames_new = [] - if filenames is None: - filenames = "." - for file in os.listdir(filenames): - if fnmatch.fnmatch(file, "FATBAND_*.lobster"): - filenames_new.append(os.path.join(filenames, file)) - filenames = filenames_new - if len(filenames) == 0: - raise ValueError("No FATBAND files in folder or given") - for filename in filenames: - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - atomnames.append(os.path.split(filename)[1].split("_")[1].capitalize()) - parameters = contents[0].split() - atomtype.append(re.split(r"[0-9]+", parameters[3])[0].capitalize()) - orbital_names.append(parameters[4]) - - # get atomtype orbital dict - atom_orbital_dict = {} - for iatom, atom in enumerate(atomnames): - if atom not in atom_orbital_dict: - atom_orbital_dict[atom] = [] - atom_orbital_dict[atom].append(orbital_names[iatom]) - # test if there are the same orbitals twice or if two different formats were used or if all necessary orbitals - # are there - for items in atom_orbital_dict.values(): - if len(set(items)) != len(items): - raise ValueError("The are two FATBAND files for the same atom and orbital. The program will stop.") - split = [] - for item in items: - split.append(item.split("_")[0]) - for number in collections.Counter(split).values(): - if number not in (1, 3, 5, 7): - raise ValueError( - "Make sure all relevant orbitals were generated and that no duplicates (2p and 2p_x) are " - "present" - ) - - kpoints_array = [] - for ifilename, filename in enumerate(filenames): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - if ifilename == 0: - self.nbands = int(parameters[6]) - self.number_kpts = kpoints_object.num_kpts - int(contents[1].split()[2]) + 1 - - if len(contents[1:]) == self.nbands + 2: - self.is_spinpolarized = False - elif len(contents[1:]) == self.nbands * 2 + 2: - self.is_spinpolarized = True - else: - linenumbers = [] - for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): - if line.split()[0] == "#": - linenumbers.append(iline) - - if ifilename == 0: - self.is_spinpolarized = len(linenumbers) == 2 - - if ifilename == 0: - eigenvals = {} - eigenvals[Spin.up] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - if self.is_spinpolarized: - eigenvals[Spin.down] = [ - [collections.defaultdict(float) for i in range(self.number_kpts)] for j in range(self.nbands) - ] - - p_eigenvals = {} - p_eigenvals[Spin.up] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - if self.is_spinpolarized: - p_eigenvals[Spin.down] = [ - [ - { - str(e): {str(orb): collections.defaultdict(float) for orb in atom_orbital_dict[e]} - for e in atomnames - } - for i in range(self.number_kpts) - ] - for j in range(self.nbands) - ] - - ikpoint = -1 - for line in contents[1:-1]: - if line.split()[0] == "#": - KPOINT = np.array( - [ - float(line.split()[4]), - float(line.split()[5]), - float(line.split()[6]), - ] - ) - if ifilename == 0: - kpoints_array.append(KPOINT) - - linenumber = 0 - iband = 0 - ikpoint += 1 - if linenumber == self.nbands: - iband = 0 - if line.split()[0] != "#": - if linenumber < self.nbands: - if ifilename == 0: - eigenvals[Spin.up][iband][ikpoint] = float(line.split()[1]) + self.efermi - - p_eigenvals[Spin.up][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - if linenumber >= self.nbands and self.is_spinpolarized: - if ifilename == 0: - eigenvals[Spin.down][iband][ikpoint] = float(line.split()[1]) + self.efermi - p_eigenvals[Spin.down][iband][ikpoint][atomnames[ifilename]][orbital_names[ifilename]] = float( - line.split()[2] - ) - - linenumber += 1 - iband += 1 - - self.kpoints_array = kpoints_array - self.eigenvals = eigenvals - self.p_eigenvals = p_eigenvals - - label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): - if label is not None: - label_dict[label] = kpoints_array[ilabel] - - self.label_dict = label_dict - - def get_bandstructure(self): - """Returns a LobsterBandStructureSymmLine object which can be plotted with a normal BSPlotter.""" - return LobsterBandStructureSymmLine( - kpoints=self.kpoints_array, - eigenvals=self.eigenvals, - lattice=self.lattice, - efermi=self.efermi, - labels_dict=self.label_dict, - structure=self.structure, - projections=self.p_eigenvals, - ) - - -class Bandoverlaps: - """ - Class to read in bandOverlaps.lobster files. These files are not created during every Lobster run. - Attributes: - bandoverlapsdict (dict[Spin, Dict[str, Dict[str, Union[float, np.ndarray]]]]): A dictionary - containing the band overlap data of the form: {spin: {"kpoint as string": {"maxDeviation": - float that describes the max deviation, "matrix": 2D array of the size number of bands - times number of bands including the overlap matrices with}}}. - maxDeviation (list[float]): A list of floats describing the maximal deviation for each problematic kpoint. - """ - - def __init__(self, filename: str = "bandOverlaps.lobster"): - """ - Args: - filename: filename of the "bandOverlaps.lobster" file. - """ - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - spin_numbers = [0, 1] if contents[0].split()[-1] == "0" else [1, 2] - - self._read(contents, spin_numbers) - - def _read(self, contents: list, spin_numbers: list): - """ - Will read in all contents of the file - - Args: - contents: list of strings - spin_numbers: list of spin numbers depending on `Lobster` version. - """ - self.bandoverlapsdict: dict[Any, dict] = {} # Any is spin number 1 or -1 - self.max_deviation = [] - # This has to be done like this because there can be different numbers of problematic k-points per spin - for line in contents: - if f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[0]}" in line: - spin = Spin.up - elif f"Overlap Matrix (abs) of the orthonormalized projected bands for spin {spin_numbers[1]}" in line: - spin = Spin.down - elif "k-point" in line: - kpoint = line.split(" ") - kpoint_array = [] - for kpointel in kpoint: - if kpointel not in ["at", "k-point", ""]: - kpoint_array.append(str(kpointel)) - - elif "maxDeviation" in line: - if spin not in self.bandoverlapsdict: - self.bandoverlapsdict[spin] = {} - if " ".join(kpoint_array) not in self.bandoverlapsdict[spin]: - self.bandoverlapsdict[spin][" ".join(kpoint_array)] = {} - maxdev = line.split(" ")[2] - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["maxDeviation"] = float(maxdev) - self.max_deviation.append(float(maxdev)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"] = [] - - else: - overlaps = [] - for el in line.split(" "): - if el not in [""]: - overlaps.append(float(el)) - self.bandoverlapsdict[spin][" ".join(kpoint_array)]["matrix"].append(overlaps) - - def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool: - """ - Will check if the maxDeviation from the ideal bandoverlap is smaller or equal to limit_maxDeviation - - Args: - limit_maxDeviation: limit of the maxDeviation - - Returns: - Boolean that will give you information about the quality of the projection. - """ - return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) - - def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, - ) -> bool: - """ - Will check if the deviation from the ideal bandoverlap of all occupied bands - is smaller or equal to limit_deviation. - - Args: - number_occ_bands_spin_up (int): number of occupied bands of spin up - number_occ_bands_spin_down (int): number of occupied bands of spin down - spin_polarized (bool): If True, then it was a spin polarized calculation - limit_deviation (float): limit of the maxDeviation - - Returns: - Boolean that will give you information about the quality of the projection - """ - for matrix in self.bandoverlapsdict[Spin.up].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if iband1 < number_occ_bands_spin_up and iband2 < number_occ_bands_spin_up: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - - if spin_polarized: - for matrix in self.bandoverlapsdict[Spin.down].values(): - for iband1, band1 in enumerate(matrix["matrix"]): - for iband2, band2 in enumerate(band1): - if number_occ_bands_spin_down is not None: - if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: - if iband1 == iband2: - if abs(band2 - 1.0) > limit_deviation: - return False - elif band2 > limit_deviation: - return False - else: - ValueError("number_occ_bands_spin_down has to be specified") - return True - - -class Grosspop: - """ - Class to read in GROSSPOP.lobster files. - - Attributes: - list_dict_grosspop (list[dict[str, str| dict[str, str]]]): List of dictionaries - including all information about the grosspopulations. Each dictionary contains the following keys: - - 'element': The element symbol of the atom. - - 'Mulliken GP': A dictionary of Mulliken gross populations, where the keys are the orbital labels and the - values are the corresponding gross populations as strings. - - 'Loewdin GP': A dictionary of Loewdin gross populations, where the keys are the orbital labels and the - values are the corresponding gross populations as strings. - The 0th entry of the list refers to the first atom in GROSSPOP.lobster and so on. - """ - - def __init__(self, filename: str = "GROSSPOP.lobster"): - """ - Args: - filename: filename of the "GROSSPOP.lobster" file. - """ - # opens file - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - - self.list_dict_grosspop = [] - # transfers content of file to list of dict - for line in contents[3:]: - cleanline = [i for i in line.split(" ") if i != ""] - if len(cleanline) == 5: - small_dict = {} - small_dict["element"] = cleanline[1] - small_dict["Mulliken GP"] = {} - small_dict["Loewdin GP"] = {} - small_dict["Mulliken GP"][cleanline[2]] = float(cleanline[3]) - small_dict["Loewdin GP"][cleanline[2]] = float(cleanline[4]) - elif len(cleanline) > 0: - small_dict["Mulliken GP"][cleanline[0]] = float(cleanline[1]) - small_dict["Loewdin GP"][cleanline[0]] = float(cleanline[2]) - if "total" in cleanline[0]: - self.list_dict_grosspop.append(small_dict) - - def get_structure_with_total_grosspop(self, structure_filename: str) -> Structure: - """ - Get a Structure with Mulliken and Loewdin total grosspopulations as site properties - - Args: - structure_filename (str): filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin total grosspopulations as site properties. - """ - struct = Structure.from_file(structure_filename) - site_properties: dict[str, Any] = {} - mullikengp = [] - loewdingp = [] - for grosspop in self.list_dict_grosspop: - mullikengp.append(grosspop["Mulliken GP"]["total"]) - loewdingp.append(grosspop["Loewdin GP"]["total"]) - - site_properties = { - "Total Mulliken GP": mullikengp, - "Total Loewdin GP": loewdingp, - } - return struct.copy(site_properties=site_properties) - - -class Wavefunction: - """ - Class to read in wave function files from Lobster and transfer them into an object of the type VolumetricData. - - Attributes: - grid (tuple[int, int, int]): Grid for the wave function [Nx+1,Ny+1,Nz+1]. - points (list[Tuple[float, float, float]]): List of points. - real (list[float]): List of real part of wave function. - imaginary (list[float]): List of imaginary part of wave function. - distance (list[float]): List of distance to first point in wave function file. - """ - - def __init__(self, filename, structure): - """ - Args: - filename: filename of wavecar file from Lobster - structure: Structure object (e.g., created by Structure.from_file("")). - """ - self.filename = filename - self.structure = structure - self.grid, self.points, self.real, self.imaginary, self.distance = Wavefunction._parse_file(filename) - - @staticmethod - def _parse_file(filename): - with zopen(filename, "rt") as f: - contents = f.read().split("\n") - points = [] - distance = [] - real = [] - imaginary = [] - splitline = contents[0].split() - grid = [int(splitline[7]), int(splitline[8]), int(splitline[9])] - for line in contents[1:]: - splitline = line.split() - if len(splitline) >= 6: - points.append([float(splitline[0]), float(splitline[1]), float(splitline[2])]) - distance.append(float(splitline[3])) - real.append(float(splitline[4])) - imaginary.append(float(splitline[5])) - - if not len(real) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - if not len(imaginary) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - return grid, points, real, imaginary, distance - - def set_volumetric_data(self, grid, structure): - """ - Will create the VolumetricData Objects. - - Args: - grid: grid on which wavefunction was calculated, e.g. [1,2,2] - structure: Structure object - """ - Nx = grid[0] - 1 - Ny = grid[1] - 1 - Nz = grid[2] - 1 - a = structure.lattice.matrix[0] - b = structure.lattice.matrix[1] - c = structure.lattice.matrix[2] - new_x = [] - new_y = [] - new_z = [] - new_real = [] - new_imaginary = [] - new_density = [] - - runner = 0 - for x in range(Nx + 1): - for y in range(Ny + 1): - for z in range(Nz + 1): - x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] - y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] - z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - - if x != Nx and y != Ny and z != Nz: - if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) - ): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x.append(x_here) - new_y.append(y_here) - new_z.append(z_here) - - new_real.append(self.real[runner]) - new_imaginary.append(self.imaginary[runner]) - new_density.append(self.real[runner] ** 2 + self.imaginary[runner] ** 2) - - runner += 1 - - self.final_real = np.reshape(new_real, [Nx, Ny, Nz]) - self.final_imaginary = np.reshape(new_imaginary, [Nx, Ny, Nz]) - self.final_density = np.reshape(new_density, [Nx, Ny, Nz]) - - self.volumetricdata_real = VolumetricData(structure, {"total": self.final_real}) - self.volumetricdata_imaginary = VolumetricData(structure, {"total": self.final_imaginary}) - self.volumetricdata_density = VolumetricData(structure, {"total": self.final_density}) - - def get_volumetricdata_real(self): - """ - Will return a VolumetricData object including the real part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_real"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_real - - def get_volumetricdata_imaginary(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_imaginary"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_imaginary - - def get_volumetricdata_density(self): - """ - Will return a VolumetricData object including the imaginary part of the wave function. - - Returns: - VolumetricData - """ - if not hasattr(self, "volumetricdata_density"): - self.set_volumetric_data(self.grid, self.structure) - return self.volumetricdata_density - - def write_file(self, filename="WAVECAR.vasp", part="real"): - """ - Will save the wavefunction in a file format that can be read by VESTA - This will only work if the wavefunction from lobster was constructed with: - "printLCAORealSpaceWavefunction kpoint 1 coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 2 3 4 - 5 6 " - or similar (the whole unit cell has to be covered!). - - Args: - filename: Filename for the output, e.g., WAVECAR.vasp - part: which part of the wavefunction will be saved ("real" or "imaginary") - """ - if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") - ): - self.set_volumetric_data(self.grid, self.structure) - if part == "real": - self.volumetricdata_real.write_file(filename) - elif part == "imaginary": - self.volumetricdata_imaginary.write_file(filename) - elif part == "density": - self.volumetricdata_density.write_file(filename) - else: - raise ValueError('part can be only "real" or "imaginary" or "density"') - - -# madleung and sitepotential classes -class MadelungEnergies: - """ - Class to read MadelungEnergies.lobster files generated by LOBSTER. - - Attributes: - madelungenergies_Mulliken (float): Float that gives the Madelung energy based on the Mulliken approach. - madelungenergies_Loewdin (float): Float that gives the Madelung energy based on the Loewdin approach. - ewald_splitting (float): Ewald splitting parameter to compute SitePotentials. - """ - - def __init__(self, filename: str = "MadelungEnergies.lobster"): - """ - Args: - filename: filename of the "MadelungEnergies.lobster" file. - """ - with zopen(filename, "rt") as f: - data = f.read().split("\n")[5] - if len(data) == 0: - raise OSError("MadelungEnergies file contains no data.") - line = data.split() - self.ewald_splitting = float(line[0]) - self.madelungenergies_Mulliken = float(line[1]) - self.madelungenergies_Loewdin = float(line[2]) - - -class SitePotential: - """ - Class to read SitePotentials.lobster files generated by LOBSTER. - - Attributes: - atomlist (list[str]): List of atoms in SitePotentials.lobster. - types (list[str]): List of types of atoms in SitePotentials.lobster. - num_atoms (int): Number of atoms in SitePotentials.lobster. - sitepotentials_Mulliken (list[float]): List of Mulliken potentials of sites in SitePotentials.lobster. - sitepotentials_Loewdin (list[float]): List of Loewdin potentials of sites in SitePotentials.lobster. - madelung_Mulliken (float): Float that gives the Madelung energy based on the Mulliken approach. - madelung_Loewdin (float): Float that gives the Madelung energy based on the Loewdin approach. - ewald_splitting (float): Ewald Splitting parameter to compute SitePotentials. - """ - - def __init__(self, filename: str = "SitePotentials.lobster"): - """ - Args: - filename: filename for the SitePotentials file, typically "SitePotentials.lobster". - """ - # site_potentials - with zopen(filename, "rt") as f: - data = f.read().split("\n") - if len(data) == 0: - raise OSError("SitePotentials file contains no data.") - - self.ewald_splitting = float(data[0].split()[9]) - - data = data[5:-1] - self.num_atoms = len(data) - 2 - self.atomlist: list[str] = [] - self.types: list[str] = [] - self.sitepotentials_Mulliken: list[float] = [] - self.sitepotentials_Loewdin: list[float] = [] - for atom in range(self.num_atoms): - line = data[atom].split() - self.atomlist.append(line[1] + str(line[0])) - self.types.append(line[1]) - self.sitepotentials_Mulliken.append(float(line[2])) - self.sitepotentials_Loewdin.append(float(line[3])) - - self.madelungenergies_Mulliken = float(data[self.num_atoms + 1].split()[3]) - self.madelungenergies_Loewdin = float(data[self.num_atoms + 1].split()[4]) - - def get_structure_with_site_potentials(self, structure_filename): - """ - Get a Structure with Mulliken and Loewdin charges as site properties - - Args: - structure_filename: filename of POSCAR - - Returns: - Structure Object with Mulliken and Loewdin charges as site properties. - """ - struct = Structure.from_file(structure_filename) - Mulliken = self.sitepotentials_Mulliken - Loewdin = self.sitepotentials_Loewdin - site_properties = { - "Mulliken Site Potentials (eV)": Mulliken, - "Loewdin Site Potentials (eV)": Loewdin, - } - return struct.copy(site_properties=site_properties) - - -def get_orb_from_str(orbs): - """ - - Args: - orbs: list of two str, e.g. ["2p_x", "3s"]. - - Returns: - list of tw Orbital objects - - """ - # TODO: also useful for plotting of dos - orb_labs = [ - "s", - "p_y", - "p_z", - "p_x", - "d_xy", - "d_yz", - "d_z^2", - "d_xz", - "d_x^2-y^2", - "f_y(3x^2-y^2)", - "f_xyz", - "f_yz^2", - "f_z^3", - "f_xz^2", - "f_z(x^2-y^2)", - "f_x(x^2-3y^2)", - ] - orbitals = [(int(orb[0]), Orbital(orb_labs.index(orb[1:]))) for orb in orbs] - orb_label = f"{orbitals[0][0]}{orbitals[0][1].name}-{orbitals[1][0]}{orbitals[1][1].name}" # type: ignore - return orb_label, orbitals From 08c6afde3e055ee9676986c1032b045981a2f0be Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Fri, 22 Sep 2023 15:39:27 +0200 Subject: [PATCH 09/26] cleaning up merging mess --- tests/io/lobster/test_inputs.py.orig | 2474 -------------------------- 1 file changed, 2474 deletions(-) delete mode 100644 tests/io/lobster/test_inputs.py.orig diff --git a/tests/io/lobster/test_inputs.py.orig b/tests/io/lobster/test_inputs.py.orig deleted file mode 100644 index d88d8bccc17..00000000000 --- a/tests/io/lobster/test_inputs.py.orig +++ /dev/null @@ -1,2474 +0,0 @@ -from __future__ import annotations - -import json -import os -import tempfile -import unittest - -import numpy as np -import pytest -from numpy.testing import assert_allclose, assert_array_equal -from pytest import approx - -from pymatgen.core.structure import Structure -from pymatgen.electronic_structure.core import Orbital, Spin -from pymatgen.io.lobster import ( - Bandoverlaps, - Charge, - Cohpcar, - Doscar, - Fatband, - Grosspop, - Icohplist, - Lobsterin, - Lobsterout, - MadelungEnergies, - Ncicobilist, - SitePotential, - Wavefunction, -) -from pymatgen.io.lobster.inputs import get_all_possible_basis_combinations -from pymatgen.io.vasp import Vasprun -from pymatgen.io.vasp.inputs import Incar, Kpoints, Potcar -from pymatgen.util.testing import TEST_FILES_DIR, PymatgenTest - -__author__ = "Janine George, Marco Esters" -__copyright__ = "Copyright 2017, The Materials Project" -__version__ = "0.2" -__email__ = "janine.george@uclouvain.be, esters@uoregon.edu" -__date__ = "Dec 10, 2017" - -test_dir_doscar = TEST_FILES_DIR - -this_dir = os.path.dirname(os.path.abspath(__file__)) - - -class TestCohpcar(PymatgenTest): - def setUp(self): - self.cohp_bise = Cohpcar(filename=f"{TEST_FILES_DIR}/cohp/COHPCAR.lobster.BiSe.gz") - self.coop_bise = Cohpcar( - filename=f"{TEST_FILES_DIR}/cohp/COOPCAR.lobster.BiSe.gz", - are_coops=True, - ) - self.cohp_fe = Cohpcar(filename=f"{TEST_FILES_DIR}/cohp/COOPCAR.lobster.gz") - self.coop_fe = Cohpcar( - filename=f"{TEST_FILES_DIR}/cohp/COOPCAR.lobster.gz", - are_coops=True, - ) - self.orb = Cohpcar(filename=f"{TEST_FILES_DIR}/cohp/COHPCAR.lobster.orbitalwise.gz") - self.orb_notot = Cohpcar(filename=f"{TEST_FILES_DIR}/cohp/COHPCAR.lobster.notot.orbitalwise.gz") - - # Lobster 3.1 (Test data is from prerelease of Lobster 3.1) - self.cohp_KF = Cohpcar(filename=f"{TEST_FILES_DIR}/cohp/COHPCAR.lobster.KF.gz") - self.coop_KF = Cohpcar( - filename=f"{TEST_FILES_DIR}/cohp/COHPCAR.lobster.KF.gz", - are_coops=True, - ) - - # example with f electrons - self.cohp_Na2UO4 = Cohpcar(filename=f"{TEST_FILES_DIR}/cohp/COHPCAR.lobster.Na2UO4.gz") - self.coop_Na2UO4 = Cohpcar( - filename=f"{TEST_FILES_DIR}/cohp/COOPCAR.lobster.Na2UO4.gz", - are_coops=True, - ) - self.cobi = Cohpcar( - filename=f"{TEST_FILES_DIR}/cohp/COBICAR.lobster.gz", - are_cobis=True, - ) - - def test_attributes(self): - assert not self.cohp_bise.are_coops - assert self.coop_bise.are_coops - assert not self.cohp_bise.is_spin_polarized - assert not self.coop_bise.is_spin_polarized - assert not self.cohp_fe.are_coops - assert self.coop_fe.are_coops - assert self.cohp_fe.is_spin_polarized - assert self.coop_fe.is_spin_polarized - assert len(self.cohp_bise.energies) == 241 - assert len(self.coop_bise.energies) == 241 - assert len(self.cohp_fe.energies) == 301 - assert len(self.coop_fe.energies) == 301 - assert len(self.cohp_bise.cohp_data) == 12 - assert len(self.coop_bise.cohp_data) == 12 - assert len(self.cohp_fe.cohp_data) == 3 - assert len(self.coop_fe.cohp_data) == 3 - - # Lobster 3.1 - assert not self.cohp_KF.are_coops - assert self.coop_KF.are_coops - assert not self.cohp_KF.is_spin_polarized - assert not self.coop_KF.is_spin_polarized - assert len(self.cohp_KF.energies) == 6 - assert len(self.coop_KF.energies) == 6 - assert len(self.cohp_KF.cohp_data) == 7 - assert len(self.coop_KF.cohp_data) == 7 - - # Lobster 4.1.0 - assert not self.cohp_KF.are_cobis - assert not self.coop_KF.are_cobis - assert not self.cobi.are_coops - assert self.cobi.are_cobis - assert not self.cobi.is_spin_polarized - - def test_energies(self): - efermi_bise = 5.90043 - elim_bise = (-0.124679, 11.9255) - efermi_fe = 9.75576 - elim_fe = (-0.277681, 14.7725) - efermi_KF = -2.87475 - elim_KF = (-11.25000 + efermi_KF, 7.5000 + efermi_KF) - - assert self.cohp_bise.efermi == efermi_bise - assert self.coop_bise.efermi == efermi_bise - assert self.cohp_fe.efermi == efermi_fe - assert self.coop_fe.efermi == efermi_fe - # Lobster 3.1 - assert self.cohp_KF.efermi == efermi_KF - assert self.coop_KF.efermi == efermi_KF - - assert self.cohp_bise.energies[0] + self.cohp_bise.efermi == approx(elim_bise[0], abs=1e-4) - assert self.cohp_bise.energies[-1] + self.cohp_bise.efermi == approx(elim_bise[1], abs=1e-4) - assert self.coop_bise.energies[0] + self.coop_bise.efermi == approx(elim_bise[0], abs=1e-4) - assert self.coop_bise.energies[-1] + self.coop_bise.efermi == approx(elim_bise[1], abs=1e-4) - - assert self.cohp_fe.energies[0] + self.cohp_fe.efermi == approx(elim_fe[0], abs=1e-4) - assert self.cohp_fe.energies[-1] + self.cohp_fe.efermi == approx(elim_fe[1], abs=1e-4) - assert self.coop_fe.energies[0] + self.coop_fe.efermi == approx(elim_fe[0], abs=1e-4) - assert self.coop_fe.energies[-1] + self.coop_fe.efermi == approx(elim_fe[1], abs=1e-4) - - # Lobster 3.1 - assert self.cohp_KF.energies[0] + self.cohp_KF.efermi == approx(elim_KF[0], abs=1e-4) - assert self.cohp_KF.energies[-1] + self.cohp_KF.efermi == approx(elim_KF[1], abs=1e-4) - assert self.coop_KF.energies[0] + self.coop_KF.efermi == approx(elim_KF[0], abs=1e-4) - assert self.coop_KF.energies[-1] + self.coop_KF.efermi == approx(elim_KF[1], abs=1e-4) - - def test_cohp_data(self): - lengths_sites_bise = { - "1": (2.882308829886294, (0, 6)), - "2": (3.1014396233274444, (0, 9)), - "3": (2.8823088298862083, (1, 7)), - "4": (3.1014396233275434, (1, 8)), - "5": (3.0500070394403904, (2, 9)), - "6": (2.9167594580335807, (2, 10)), - "7": (3.05000703944039, (3, 8)), - "8": (2.9167594580335803, (3, 11)), - "9": (3.3752173204052101, (4, 11)), - "10": (3.0729354518345948, (4, 5)), - "11": (3.3752173204052101, (5, 10)), - } - lengths_sites_fe = { - "1": (2.8318907764979082, (7, 6)), - "2": (2.4524893531900283, (7, 8)), - } - # Lobster 3.1 - lengths_sites_KF = { - "1": (2.7119923200622269, (0, 1)), - "2": (2.7119923200622269, (0, 1)), - "3": (2.7119923576010501, (0, 1)), - "4": (2.7119923576010501, (0, 1)), - "5": (2.7119923200622269, (0, 1)), - "6": (2.7119923200622269, (0, 1)), - } - - for data in [self.cohp_bise.cohp_data, self.coop_bise.cohp_data]: - for bond, val in data.items(): - if bond != "average": - assert val["length"] == lengths_sites_bise[bond][0] - assert val["sites"] == lengths_sites_bise[bond][1] - assert len(val["COHP"][Spin.up]) == 241 - assert len(val["ICOHP"][Spin.up]) == 241 - for data in [self.cohp_fe.cohp_data, self.coop_fe.cohp_data]: - for bond, val in data.items(): - if bond != "average": - assert val["length"] == lengths_sites_fe[bond][0] - assert val["sites"] == lengths_sites_fe[bond][1] - assert len(val["COHP"][Spin.up]) == 301 - assert len(val["ICOHP"][Spin.up]) == 301 - - # Lobster 3.1 - for data in [self.cohp_KF.cohp_data, self.coop_KF.cohp_data]: - for bond, val in data.items(): - if bond != "average": - assert val["length"] == lengths_sites_KF[bond][0] - assert val["sites"] == lengths_sites_KF[bond][1] - assert len(val["COHP"][Spin.up]) == 6 - assert len(val["ICOHP"][Spin.up]) == 6 - - def test_orbital_resolved_cohp(self): - orbitals = [(Orbital(i), Orbital(j)) for j in range(4) for i in range(4)] - assert self.cohp_bise.orb_res_cohp is None - assert self.coop_bise.orb_res_cohp is None - assert self.cohp_fe.orb_res_cohp is None - assert self.coop_fe.orb_res_cohp is None - assert self.orb_notot.cohp_data["1"]["COHP"] is None - assert self.orb_notot.cohp_data["1"]["ICOHP"] is None - for orbs in self.orb.orb_res_cohp["1"]: - orb_set = self.orb.orb_res_cohp["1"][orbs]["orbitals"] - assert orb_set[0][0] == 4 - assert orb_set[1][0] == 4 - assert (orb_set[0][1], orb_set[1][1]) in orbitals - - # test d and f orbitals - comparelist = [ - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 6, - 7, - 7, - 7, - 7, - ] - comparelist2 = [ - "f0", - "f0", - "f0", - "f0", - "f1", - "f1", - "f1", - "f1", - "f2", - "f2", - "f2", - "f2", - "f3", - "f3", - "f3", - "f3", - "f_1", - "f_1", - "f_1", - "f_1", - "f_2", - "f_2", - "f_2", - "f_2", - "f_3", - "f_3", - "f_3", - "f_3", - "dx2", - "dx2", - "dx2", - "dx2", - "dxy", - "dxy", - "dxy", - "dxy", - "dxz", - "dxz", - "dxz", - "dxz", - "dyz", - "dyz", - "dyz", - "dyz", - "dz2", - "dz2", - "dz2", - "dz2", - "px", - "px", - "px", - "px", - "py", - "py", - "py", - "py", - "pz", - "pz", - "pz", - "pz", - "s", - "s", - "s", - "s", - "s", - "s", - "s", - "s", - ] - for iorb, orbs in enumerate(sorted(self.cohp_Na2UO4.orb_res_cohp["49"])): - orb_set = self.cohp_Na2UO4.orb_res_cohp["49"][orbs]["orbitals"] - assert orb_set[0][0] == comparelist[iorb] - assert str(orb_set[0][1]) == comparelist2[iorb] - - # The sum of the orbital-resolved COHPs should be approximately - # the total COHP. Due to small deviations in the LOBSTER calculation, - # the precision is not very high though. - cohp = self.orb.cohp_data["1"]["COHP"][Spin.up] - icohp = self.orb.cohp_data["1"]["ICOHP"][Spin.up] - tot = np.sum( - [self.orb.orb_res_cohp["1"][orbs]["COHP"][Spin.up] for orbs in self.orb.orb_res_cohp["1"]], - axis=0, - ) - assert_allclose(tot, cohp, atol=1e-3) - tot = np.sum( - [self.orb.orb_res_cohp["1"][orbs]["ICOHP"][Spin.up] for orbs in self.orb.orb_res_cohp["1"]], - axis=0, - ) - assert_allclose(tot, icohp, atol=1e-3) - - # Lobster 3.1 - cohp_KF = self.cohp_KF.cohp_data["1"]["COHP"][Spin.up] - icohp_KF = self.cohp_KF.cohp_data["1"]["ICOHP"][Spin.up] - tot_KF = np.sum( - [self.cohp_KF.orb_res_cohp["1"][orbs]["COHP"][Spin.up] for orbs in self.cohp_KF.orb_res_cohp["1"]], - axis=0, - ) - assert_allclose(tot_KF, cohp_KF, atol=1e-3) - tot_KF = np.sum( - [self.cohp_KF.orb_res_cohp["1"][orbs]["ICOHP"][Spin.up] for orbs in self.cohp_KF.orb_res_cohp["1"]], - axis=0, - ) - assert_allclose(tot_KF, icohp_KF, atol=1e-3) - - # d and f orbitals - cohp_Na2UO4 = self.cohp_Na2UO4.cohp_data["49"]["COHP"][Spin.up] - icohp_Na2UO4 = self.cohp_Na2UO4.cohp_data["49"]["ICOHP"][Spin.up] - tot_Na2UO4 = np.sum( - [ - self.cohp_Na2UO4.orb_res_cohp["49"][orbs]["COHP"][Spin.up] - for orbs in self.cohp_Na2UO4.orb_res_cohp["49"] - ], - axis=0, - ) - assert_allclose(tot_Na2UO4, cohp_Na2UO4, atol=1e-3) - tot_Na2UO4 = np.sum( - [ - self.cohp_Na2UO4.orb_res_cohp["49"][orbs]["ICOHP"][Spin.up] - for orbs in self.cohp_Na2UO4.orb_res_cohp["49"] - ], - axis=0, - ) - assert_allclose(tot_Na2UO4, icohp_Na2UO4, atol=1e-3) - - -class TestIcohplist(unittest.TestCase): - def setUp(self): - self.icohp_bise = Icohplist(filename=f"{TEST_FILES_DIR}/cohp/ICOHPLIST.lobster.BiSe") - self.icoop_bise = Icohplist( - filename=f"{TEST_FILES_DIR}/cohp/ICOOPLIST.lobster.BiSe", - are_coops=True, - ) - self.icohp_fe = Icohplist(filename=f"{TEST_FILES_DIR}/cohp/ICOHPLIST.lobster") - # allow gzipped files - self.icohp_gzipped = Icohplist(filename=f"{TEST_FILES_DIR}/cohp/ICOHPLIST.lobster.gz") - self.icoop_fe = Icohplist( - filename=f"{TEST_FILES_DIR}/cohp/ICOHPLIST.lobster", - are_coops=True, - ) - # ICOBIs and orbitalwise ICOBILIST.lobster - self.icobi_orbitalwise = Icohplist( - filename=f"{TEST_FILES_DIR}/cohp/ICOBILIST.lobster", - are_cobis=True, - ) - # TODO: test orbitalwise ICOHPs with and without spin polarization - - self.icobi = Icohplist( - filename=f"{TEST_FILES_DIR}/cohp/ICOBILIST.lobster.withoutorbitals", - are_cobis=True, - ) - self.icobi_orbitalwise_spinpolarized = Icohplist( - filename=f"{TEST_FILES_DIR}/cohp/ICOBILIST.lobster.spinpolarized", - are_cobis=True, - ) - # make sure the correct line is read to check if this is a orbitalwise ICOBILIST - self.icobi_orbitalwise_add = Icohplist( - filename=f"{TEST_FILES_DIR}/cohp/ICOBILIST.lobster.additional_case", - are_cobis=True, - ) - self.icobi_orbitalwise_spinpolarized_add = Icohplist( - filename=os.path.join( - TEST_FILES_DIR, - "cohp", - "ICOBILIST.lobster.spinpolarized.additional_case", - ), - are_cobis=True, - ) - - def test_attributes(self): - assert not self.icohp_bise.are_coops - assert self.icoop_bise.are_coops - assert not self.icohp_bise.is_spin_polarized - assert not self.icoop_bise.is_spin_polarized - assert len(self.icohp_bise.icohplist) == 11 - assert len(self.icoop_bise.icohplist) == 11 - assert not self.icohp_fe.are_coops - assert self.icoop_fe.are_coops - assert self.icohp_fe.is_spin_polarized - assert self.icoop_fe.is_spin_polarized - assert len(self.icohp_fe.icohplist) == 2 - assert len(self.icoop_fe.icohplist) == 2 - # test are_cobis - assert not self.icohp_fe.are_coops - assert not self.icohp_fe.are_cobis - assert self.icoop_fe.are_coops - assert not self.icoop_fe.are_cobis - assert self.icobi.are_cobis - assert not self.icobi.are_coops - - # orbitalwise - assert self.icobi_orbitalwise.orbitalwise - assert not self.icobi.orbitalwise - - assert self.icobi_orbitalwise_spinpolarized.orbitalwise - - assert self.icobi_orbitalwise_add.orbitalwise - assert self.icobi_orbitalwise_spinpolarized_add.orbitalwise - - def test_values(self): - icohplist_bise = { - "1": { - "length": 2.88231, - "number_of_bonds": 3, - "icohp": {Spin.up: -2.18042}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "2": { - "length": 3.10144, - "number_of_bonds": 3, - "icohp": {Spin.up: -1.14347}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "3": { - "length": 2.88231, - "number_of_bonds": 3, - "icohp": {Spin.up: -2.18042}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "4": { - "length": 3.10144, - "number_of_bonds": 3, - "icohp": {Spin.up: -1.14348}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "5": { - "length": 3.05001, - "number_of_bonds": 3, - "icohp": {Spin.up: -1.30006}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "6": { - "length": 2.91676, - "number_of_bonds": 3, - "icohp": {Spin.up: -1.96843}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "7": { - "length": 3.05001, - "number_of_bonds": 3, - "icohp": {Spin.up: -1.30006}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "8": { - "length": 2.91676, - "number_of_bonds": 3, - "icohp": {Spin.up: -1.96843}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "9": { - "length": 3.37522, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.47531}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "10": { - "length": 3.07294, - "number_of_bonds": 3, - "icohp": {Spin.up: -2.38796}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "11": { - "length": 3.37522, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.47531}, - "translation": [0, 0, 0], - "orbitals": None, - }, - } - icooplist_bise = { - "1": { - "length": 2.88231, - "number_of_bonds": 3, - "icohp": {Spin.up: 0.14245}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "2": { - "length": 3.10144, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.04118}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "3": { - "length": 2.88231, - "number_of_bonds": 3, - "icohp": {Spin.up: 0.14245}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "4": { - "length": 3.10144, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.04118}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "5": { - "length": 3.05001, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.03516}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "6": { - "length": 2.91676, - "number_of_bonds": 3, - "icohp": {Spin.up: 0.10745}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "7": { - "length": 3.05001, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.03516}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "8": { - "length": 2.91676, - "number_of_bonds": 3, - "icohp": {Spin.up: 0.10745}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "9": { - "length": 3.37522, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.12395}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "10": { - "length": 3.07294, - "number_of_bonds": 3, - "icohp": {Spin.up: 0.24714}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "11": { - "length": 3.37522, - "number_of_bonds": 3, - "icohp": {Spin.up: -0.12395}, - "translation": [0, 0, 0], - "orbitals": None, - }, - } - icooplist_fe = { - "1": { - "length": 2.83189, - "number_of_bonds": 2, - "icohp": {Spin.up: -0.10218, Spin.down: -0.19701}, - "translation": [0, 0, 0], - "orbitals": None, - }, - "2": { - "length": 2.45249, - "number_of_bonds": 1, - "icohp": {Spin.up: -0.28485, Spin.down: -0.58279}, - "translation": [0, 0, 0], - "orbitals": None, - }, - } - - assert icohplist_bise == self.icohp_bise.icohplist - assert self.icohp_bise.icohpcollection.extremum_icohpvalue() == -2.38796 - assert icooplist_fe == self.icoop_fe.icohplist - assert self.icoop_fe.icohpcollection.extremum_icohpvalue() == -0.29919 - assert icooplist_bise == self.icoop_bise.icohplist - assert self.icoop_bise.icohpcollection.extremum_icohpvalue() == 0.24714 - assert self.icobi.icohplist["1"]["icohp"][Spin.up] == approx(0.58649) - assert self.icobi_orbitalwise.icohplist["2"]["icohp"][Spin.up] == approx(0.58649) - assert self.icobi_orbitalwise.icohplist["1"]["icohp"][Spin.up] == approx(0.58649) - assert self.icobi_orbitalwise_spinpolarized.icohplist["1"]["icohp"][Spin.up] == approx(0.58649 / 2, abs=1e-3) - assert self.icobi_orbitalwise_spinpolarized.icohplist["1"]["icohp"][Spin.down] == approx(0.58649 / 2, abs=1e-3) - assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["icohp"][Spin.down] == approx(0.58649 / 2, abs=1e-3) - assert self.icobi.icohpcollection.extremum_icohpvalue() == 0.58649 - assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["orbitals"]["2s-6s"]["icohp"][Spin.up] == 0.0247 - - -<<<<<<< HEAD:pymatgen/io/lobster/tests/test_lobster.py -class NcicobilistTest(unittest.TestCase): - def setUp(self): - self.ncicobi = Ncicobilist(filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster")) - - self.ncicobigz = Ncicobilist( - filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.gz") - ) - - self.ncicobinospin = Ncicobilist( - filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.nospin") - ) - - self.ncicobinospinwo = Ncicobilist( - filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.nospin.withoutorbitals") - ) - - self.ncicobiwo = Ncicobilist( - filename=os.path.join(PymatgenTest.TEST_FILES_DIR, "cohp", "NcICOBILIST.lobster.withoutorbitals") - ) - - def test_ncicobilist(self): - assert self.ncicobi.is_spin_polarized - assert not self.ncicobinospin.is_spin_polarized - assert self.ncicobiwo.is_spin_polarized - assert not self.ncicobinospinwo.is_spin_polarized - assert self.ncicobi.orbitalwise - assert self.ncicobinospin.orbitalwise - assert not self.ncicobiwo.orbitalwise - assert not self.ncicobinospinwo.orbitalwise - assert len(self.ncicobi.ncicobilist) == 2 - assert self.ncicobi.ncicobilist["2"]["number_of_atoms"] == 3 - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == approx(0.00009) - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down] == approx(0.00009) - assert self.ncicobi.ncicobilist["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobigz.ncicobilist["2"]["interaction_type"] - assert sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())) ==approx(self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up]) - - -class DoscarTest(unittest.TestCase): -======= -class TestDoscar(unittest.TestCase): ->>>>>>> master:tests/io/lobster/test_inputs.py - def setUp(self): - # first for spin polarized version - doscar = f"{test_dir_doscar}/DOSCAR.lobster.spin" - poscar = f"{test_dir_doscar}/POSCAR.lobster.spin_DOS" - # not spin polarized - doscar2 = f"{test_dir_doscar}/DOSCAR.lobster.nonspin" - poscar2 = f"{test_dir_doscar}/POSCAR.lobster.nonspin_DOS" - f"{test_dir_doscar}/DOSCAR.lobster.nonspin_zip.gz" - f"{test_dir_doscar}/POSCAR.lobster.nonspin_DOS_zip.gz" - self.DOSCAR_spin_pol = Doscar(doscar=doscar, structure_file=poscar) - self.DOSCAR_nonspin_pol = Doscar(doscar=doscar2, structure_file=poscar2) - - self.DOSCAR_spin_pol = Doscar(doscar=doscar, structure_file=poscar) - self.DOSCAR_nonspin_pol = Doscar(doscar=doscar2, structure_file=poscar2) - - with open(f"{test_dir_doscar}/structure_KF.json") as f: - data = json.load(f) - - self.structure = Structure.from_dict(data) - - # test structure argument - self.DOSCAR_spin_pol2 = Doscar(doscar=doscar, structure_file=None, structure=Structure.from_file(poscar)) - - def test_complete_dos(self): - # first for spin polarized version - energies_spin = [-11.25000, -7.50000, -3.75000, 0.00000, 3.75000, 7.50000] - tdos_up = [0.00000, 0.79999, 0.00000, 0.79999, 0.00000, 0.02577] - tdos_down = [0.00000, 0.79999, 0.00000, 0.79999, 0.00000, 0.02586] - fermi = 0.0 - - PDOS_F_2s_up = [0.00000, 0.00159, 0.00000, 0.00011, 0.00000, 0.00069] - PDOS_F_2s_down = [0.00000, 0.00159, 0.00000, 0.00011, 0.00000, 0.00069] - PDOS_F_2py_up = [0.00000, 0.00160, 0.00000, 0.25801, 0.00000, 0.00029] - PDOS_F_2py_down = [0.00000, 0.00161, 0.00000, 0.25819, 0.00000, 0.00029] - PDOS_F_2pz_up = [0.00000, 0.00161, 0.00000, 0.25823, 0.00000, 0.00029] - PDOS_F_2pz_down = [0.00000, 0.00160, 0.00000, 0.25795, 0.00000, 0.00029] - PDOS_F_2px_up = [0.00000, 0.00160, 0.00000, 0.25805, 0.00000, 0.00029] - PDOS_F_2px_down = [0.00000, 0.00161, 0.00000, 0.25814, 0.00000, 0.00029] - - assert energies_spin == self.DOSCAR_spin_pol.completedos.energies.tolist() - assert tdos_up == self.DOSCAR_spin_pol.completedos.densities[Spin.up].tolist() - assert tdos_down == self.DOSCAR_spin_pol.completedos.densities[Spin.down].tolist() - assert fermi == approx(self.DOSCAR_spin_pol.completedos.efermi) - - assert_allclose( - self.DOSCAR_spin_pol.completedos.structure.frac_coords, - self.structure.frac_coords, - ) - assert_allclose( - self.DOSCAR_spin_pol2.completedos.structure.frac_coords, - self.structure.frac_coords, - ) - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2s"][Spin.up].tolist() == PDOS_F_2s_up - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2s"][Spin.down].tolist() == PDOS_F_2s_down - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2p_y"][Spin.up].tolist() == PDOS_F_2py_up - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2p_y"][Spin.down].tolist() == PDOS_F_2py_down - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2p_z"][Spin.up].tolist() == PDOS_F_2pz_up - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2p_z"][Spin.down].tolist() == PDOS_F_2pz_down - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2p_x"][Spin.up].tolist() == PDOS_F_2px_up - assert self.DOSCAR_spin_pol.completedos.pdos[self.structure[0]]["2p_x"][Spin.down].tolist() == PDOS_F_2px_down - - energies_nonspin = [-11.25000, -7.50000, -3.75000, 0.00000, 3.75000, 7.50000] - tdos_nonspin = [0.00000, 1.60000, 0.00000, 1.60000, 0.00000, 0.02418] - PDOS_F_2s = [0.00000, 0.00320, 0.00000, 0.00017, 0.00000, 0.00060] - PDOS_F_2py = [0.00000, 0.00322, 0.00000, 0.51635, 0.00000, 0.00037] - PDOS_F_2pz = [0.00000, 0.00322, 0.00000, 0.51636, 0.00000, 0.00037] - PDOS_F_2px = [0.00000, 0.00322, 0.00000, 0.51634, 0.00000, 0.00037] - - assert energies_nonspin == self.DOSCAR_nonspin_pol.completedos.energies.tolist() - - assert tdos_nonspin == self.DOSCAR_nonspin_pol.completedos.densities[Spin.up].tolist() - - assert fermi == approx(self.DOSCAR_nonspin_pol.completedos.efermi) - - assert self.DOSCAR_nonspin_pol.completedos.structure == self.structure - - assert self.DOSCAR_nonspin_pol.completedos.pdos[self.structure[0]]["2s"][Spin.up].tolist() == PDOS_F_2s - assert self.DOSCAR_nonspin_pol.completedos.pdos[self.structure[0]]["2p_y"][Spin.up].tolist() == PDOS_F_2py - assert self.DOSCAR_nonspin_pol.completedos.pdos[self.structure[0]]["2p_z"][Spin.up].tolist() == PDOS_F_2pz - assert self.DOSCAR_nonspin_pol.completedos.pdos[self.structure[0]]["2p_x"][Spin.up].tolist() == PDOS_F_2px - - def test_pdos(self): - # first for spin polarized version - - PDOS_F_2s_up = [0.00000, 0.00159, 0.00000, 0.00011, 0.00000, 0.00069] - PDOS_F_2s_down = [0.00000, 0.00159, 0.00000, 0.00011, 0.00000, 0.00069] - PDOS_F_2py_up = [0.00000, 0.00160, 0.00000, 0.25801, 0.00000, 0.00029] - PDOS_F_2py_down = [0.00000, 0.00161, 0.00000, 0.25819, 0.00000, 0.00029] - PDOS_F_2pz_up = [0.00000, 0.00161, 0.00000, 0.25823, 0.00000, 0.00029] - PDOS_F_2pz_down = [0.00000, 0.00160, 0.00000, 0.25795, 0.00000, 0.00029] - PDOS_F_2px_up = [0.00000, 0.00160, 0.00000, 0.25805, 0.00000, 0.00029] - PDOS_F_2px_down = [0.00000, 0.00161, 0.00000, 0.25814, 0.00000, 0.00029] - - assert self.DOSCAR_spin_pol.pdos[0]["2s"][Spin.up].tolist() == PDOS_F_2s_up - assert self.DOSCAR_spin_pol.pdos[0]["2s"][Spin.down].tolist() == PDOS_F_2s_down - assert self.DOSCAR_spin_pol.pdos[0]["2p_y"][Spin.up].tolist() == PDOS_F_2py_up - assert self.DOSCAR_spin_pol.pdos[0]["2p_y"][Spin.down].tolist() == PDOS_F_2py_down - assert self.DOSCAR_spin_pol.pdos[0]["2p_z"][Spin.up].tolist() == PDOS_F_2pz_up - assert self.DOSCAR_spin_pol.pdos[0]["2p_z"][Spin.down].tolist() == PDOS_F_2pz_down - assert self.DOSCAR_spin_pol.pdos[0]["2p_x"][Spin.up].tolist() == PDOS_F_2px_up - assert self.DOSCAR_spin_pol.pdos[0]["2p_x"][Spin.down].tolist() == PDOS_F_2px_down - - # non spin - PDOS_F_2s = [0.00000, 0.00320, 0.00000, 0.00017, 0.00000, 0.00060] - PDOS_F_2py = [0.00000, 0.00322, 0.00000, 0.51635, 0.00000, 0.00037] - PDOS_F_2pz = [0.00000, 0.00322, 0.00000, 0.51636, 0.00000, 0.00037] - PDOS_F_2px = [0.00000, 0.00322, 0.00000, 0.51634, 0.00000, 0.00037] - - assert self.DOSCAR_nonspin_pol.pdos[0]["2s"][Spin.up].tolist() == PDOS_F_2s - assert self.DOSCAR_nonspin_pol.pdos[0]["2p_y"][Spin.up].tolist() == PDOS_F_2py - assert self.DOSCAR_nonspin_pol.pdos[0]["2p_z"][Spin.up].tolist() == PDOS_F_2pz - assert self.DOSCAR_nonspin_pol.pdos[0]["2p_x"][Spin.up].tolist() == PDOS_F_2px - - def test_tdos(self): - # first for spin polarized version - energies_spin = [-11.25000, -7.50000, -3.75000, 0.00000, 3.75000, 7.50000] - tdos_up = [0.00000, 0.79999, 0.00000, 0.79999, 0.00000, 0.02577] - tdos_down = [0.00000, 0.79999, 0.00000, 0.79999, 0.00000, 0.02586] - fermi = 0.0 - - assert energies_spin == self.DOSCAR_spin_pol.tdos.energies.tolist() - assert tdos_up == self.DOSCAR_spin_pol.tdos.densities[Spin.up].tolist() - assert tdos_down == self.DOSCAR_spin_pol.tdos.densities[Spin.down].tolist() - assert fermi == approx(self.DOSCAR_spin_pol.tdos.efermi) - - energies_nonspin = [-11.25000, -7.50000, -3.75000, 0.00000, 3.75000, 7.50000] - tdos_nonspin = [0.00000, 1.60000, 0.00000, 1.60000, 0.00000, 0.02418] - fermi = 0.0 - - assert energies_nonspin == self.DOSCAR_nonspin_pol.tdos.energies.tolist() - assert tdos_nonspin == self.DOSCAR_nonspin_pol.tdos.densities[Spin.up].tolist() - assert fermi == approx(self.DOSCAR_nonspin_pol.tdos.efermi) - - def test_energies(self): - # first for spin polarized version - energies_spin = [-11.25000, -7.50000, -3.75000, 0.00000, 3.75000, 7.50000] - - assert energies_spin == self.DOSCAR_spin_pol.energies.tolist() - - energies_nonspin = [-11.25000, -7.50000, -3.75000, 0.00000, 3.75000, 7.50000] - assert energies_nonspin == self.DOSCAR_nonspin_pol.energies.tolist() - - def test_tdensities(self): - # first for spin polarized version - tdos_up = [0.00000, 0.79999, 0.00000, 0.79999, 0.00000, 0.02577] - tdos_down = [0.00000, 0.79999, 0.00000, 0.79999, 0.00000, 0.02586] - - assert tdos_up == self.DOSCAR_spin_pol.tdensities[Spin.up].tolist() - assert tdos_down == self.DOSCAR_spin_pol.tdensities[Spin.down].tolist() - - tdos_nonspin = [0.00000, 1.60000, 0.00000, 1.60000, 0.00000, 0.02418] - assert tdos_nonspin == self.DOSCAR_nonspin_pol.tdensities[Spin.up].tolist() - - def test_itdensities(self): - itdos_up = [1.99997, 4.99992, 4.99992, 7.99987, 7.99987, 8.09650] - itdos_down = [1.99997, 4.99992, 4.99992, 7.99987, 7.99987, 8.09685] - assert itdos_up == self.DOSCAR_spin_pol.itdensities[Spin.up].tolist() - assert itdos_down == self.DOSCAR_spin_pol.itdensities[Spin.down].tolist() - - itdos_nonspin = [4.00000, 10.00000, 10.00000, 16.00000, 16.00000, 16.09067] - assert itdos_nonspin == self.DOSCAR_nonspin_pol.itdensities[Spin.up].tolist() - - def test_is_spin_polarized(self): - # first for spin polarized version - assert self.DOSCAR_spin_pol.is_spin_polarized - - assert not self.DOSCAR_nonspin_pol.is_spin_polarized - - -class TestCharge(PymatgenTest): - def setUp(self): - self.charge2 = Charge(filename=f"{TEST_FILES_DIR}/cohp/CHARGE.lobster.MnO") - # gzipped file - self.charge = Charge(filename=f"{TEST_FILES_DIR}/cohp/CHARGE.lobster.MnO2.gz") - - def testattributes(self): - charge_Loewdin = [-1.25, 1.25] - charge_Mulliken = [-1.30, 1.30] - atomlist = ["O1", "Mn2"] - types = ["O", "Mn"] - num_atoms = 2 - assert_array_equal(charge_Mulliken, self.charge2.Mulliken) - assert_array_equal(charge_Loewdin, self.charge2.Loewdin) - assert_array_equal(atomlist, self.charge2.atomlist) - assert_array_equal(types, self.charge2.types) - assert_array_equal(num_atoms, self.charge2.num_atoms) - - def test_get_structure_with_charges(self): - structure_dict2 = { - "lattice": { - "c": 3.198244, - "volume": 23.132361565928807, - "b": 3.1982447183003364, - "gamma": 60.00000011873414, - "beta": 60.00000401737447, - "alpha": 60.00000742944491, - "matrix": [ - [2.769761, 0.0, 1.599122], - [0.923254, 2.611356, 1.599122], - [0.0, 0.0, 3.198244], - ], - "a": 3.1982443884113985, - }, - "@class": "Structure", - "sites": [ - { - "xyz": [1.846502883732, 1.305680611356, 3.198248797366], - "properties": {"Loewdin Charges": -1.25, "Mulliken Charges": -1.3}, - "abc": [0.499998, 0.500001, 0.500002], - "species": [{"occu": 1, "element": "O"}], - "label": "O", - }, - { - "xyz": [0.0, 0.0, 0.0], - "properties": {"Loewdin Charges": 1.25, "Mulliken Charges": 1.3}, - "abc": [0.0, 0.0, 0.0], - "species": [{"occu": 1, "element": "Mn"}], - "label": "Mn", - }, - ], - "charge": None, - "@module": "pymatgen.core.structure", - } - s2 = Structure.from_dict(structure_dict2) - assert s2 == self.charge2.get_structure_with_charges(TEST_FILES_DIR / "POSCAR.MnO") - - -class TestLobsterout(PymatgenTest): - def setUp(self): - self.lobsterout_normal = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.normal") - # make sure .gz files are also read correctly - self.lobsterout_normal = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.normal2.gz") - self.lobsterout_fatband_grosspop_densityofenergies = Lobsterout( - filename=os.path.join( - TEST_FILES_DIR, - "cohp", - "lobsterout.fatband_grosspop_densityofenergy", - ) - ) - self.lobsterout_saveprojection = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.saveprojection") - self.lobsterout_skipping_all = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.skipping_all") - self.lobsterout_twospins = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.twospins") - self.lobsterout_GaAs = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.GaAs") - self.lobsterout_from_projection = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout_from_projection") - self.lobsterout_onethread = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout.onethread") - self.lobsterout_cobi_madelung = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout_cobi_madelung") - self.lobsterout_doscar_lso = Lobsterout(filename=f"{TEST_FILES_DIR}/cohp/lobsterout_doscar_lso") - - # TODO: implement skipping madelung/cobi - self.lobsterout_skipping_cobi_madelung = Lobsterout( - filename=f"{TEST_FILES_DIR}/cohp/lobsterout.skip_cobi_madelung" - ) - - def testattributes(self): - assert self.lobsterout_normal.basis_functions == [ - [ - "3s", - "4s", - "3p_y", - "3p_z", - "3p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ] - ] - assert self.lobsterout_normal.basis_type == ["pbeVaspFit2015"] - assert self.lobsterout_normal.charge_spilling == [0.0268] - assert self.lobsterout_normal.dft_program == "VASP" - assert self.lobsterout_normal.elements == ["Ti"] - assert self.lobsterout_normal.has_charge - assert self.lobsterout_normal.has_cohpcar - assert self.lobsterout_normal.has_coopcar - assert self.lobsterout_normal.has_doscar - assert not self.lobsterout_normal.has_projection - assert self.lobsterout_normal.has_bandoverlaps - assert not self.lobsterout_normal.has_density_of_energies - assert not self.lobsterout_normal.has_fatbands - assert not self.lobsterout_normal.has_grosspopulation - assert self.lobsterout_normal.info_lines == [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 21 and upwards will be ignored.", - ] - assert self.lobsterout_normal.info_orthonormalization == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5." - ] - assert not self.lobsterout_normal.is_restart_from_projection - assert self.lobsterout_normal.lobster_version == "v3.1.0" - assert self.lobsterout_normal.number_of_spins == 1 - assert self.lobsterout_normal.number_of_threads == 8 - assert self.lobsterout_normal.timing == { - "wall_time": {"h": "0", "min": "0", "s": "2", "ms": "702"}, - "user_time": {"h": "0", "min": "0", "s": "20", "ms": "330"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "310"}, - } - assert self.lobsterout_normal.total_spilling[0] == approx([0.044000000000000004][0]) - assert self.lobsterout_normal.warning_lines == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5.", - "Generally, this is not a critical error. But to help you analyze it,", - "I dumped the band overlap matrices to the file bandOverlaps.lobster.", - "Please check how much they deviate from the identity matrix and decide to", - "use your results only, if you are sure that this is ok.", - ] - - assert self.lobsterout_fatband_grosspop_densityofenergies.basis_functions == [ - [ - "3s", - "4s", - "3p_y", - "3p_z", - "3p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ] - ] - assert self.lobsterout_fatband_grosspop_densityofenergies.basis_type == ["pbeVaspFit2015"] - assert self.lobsterout_fatband_grosspop_densityofenergies.charge_spilling == [0.0268] - assert self.lobsterout_fatband_grosspop_densityofenergies.dft_program == "VASP" - assert self.lobsterout_fatband_grosspop_densityofenergies.elements == ["Ti"] - assert self.lobsterout_fatband_grosspop_densityofenergies.has_charge - assert not self.lobsterout_fatband_grosspop_densityofenergies.has_cohpcar - assert not self.lobsterout_fatband_grosspop_densityofenergies.has_coopcar - assert not self.lobsterout_fatband_grosspop_densityofenergies.has_doscar - assert not self.lobsterout_fatband_grosspop_densityofenergies.has_projection - assert self.lobsterout_fatband_grosspop_densityofenergies.has_bandoverlaps - assert self.lobsterout_fatband_grosspop_densityofenergies.has_density_of_energies - assert self.lobsterout_fatband_grosspop_densityofenergies.has_fatbands - assert self.lobsterout_fatband_grosspop_densityofenergies.has_grosspopulation - assert self.lobsterout_fatband_grosspop_densityofenergies.info_lines == [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 21 and upwards will be ignored.", - ] - assert self.lobsterout_fatband_grosspop_densityofenergies.info_orthonormalization == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5." - ] - assert not self.lobsterout_fatband_grosspop_densityofenergies.is_restart_from_projection - assert self.lobsterout_fatband_grosspop_densityofenergies.lobster_version == "v3.1.0" - assert self.lobsterout_fatband_grosspop_densityofenergies.number_of_spins == 1 - assert self.lobsterout_fatband_grosspop_densityofenergies.number_of_threads == 8 - assert self.lobsterout_fatband_grosspop_densityofenergies.timing == { - "wall_time": {"h": "0", "min": "0", "s": "4", "ms": "136"}, - "user_time": {"h": "0", "min": "0", "s": "18", "ms": "280"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "290"}, - } - assert self.lobsterout_fatband_grosspop_densityofenergies.total_spilling[0] == approx([0.044000000000000004][0]) - assert self.lobsterout_fatband_grosspop_densityofenergies.warning_lines == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5.", - "Generally, this is not a critical error. But to help you analyze it,", - "I dumped the band overlap matrices to the file bandOverlaps.lobster.", - "Please check how much they deviate from the identity matrix and decide to", - "use your results only, if you are sure that this is ok.", - ] - - assert self.lobsterout_saveprojection.basis_functions == [ - [ - "3s", - "4s", - "3p_y", - "3p_z", - "3p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ] - ] - assert self.lobsterout_saveprojection.basis_type == ["pbeVaspFit2015"] - assert self.lobsterout_saveprojection.charge_spilling == [0.0268] - assert self.lobsterout_saveprojection.dft_program == "VASP" - assert self.lobsterout_saveprojection.elements == ["Ti"] - assert self.lobsterout_saveprojection.has_charge - assert not self.lobsterout_saveprojection.has_cohpcar - assert not self.lobsterout_saveprojection.has_coopcar - assert not self.lobsterout_saveprojection.has_doscar - assert self.lobsterout_saveprojection.has_projection - assert self.lobsterout_saveprojection.has_bandoverlaps - assert self.lobsterout_saveprojection.has_density_of_energies - assert not self.lobsterout_saveprojection.has_fatbands - assert not self.lobsterout_saveprojection.has_grosspopulation - assert self.lobsterout_saveprojection.info_lines == [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 21 and upwards will be ignored.", - ] - assert self.lobsterout_saveprojection.info_orthonormalization == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5." - ] - assert not self.lobsterout_saveprojection.is_restart_from_projection - assert self.lobsterout_saveprojection.lobster_version == "v3.1.0" - assert self.lobsterout_saveprojection.number_of_spins == 1 - assert self.lobsterout_saveprojection.number_of_threads == 8 - assert self.lobsterout_saveprojection.timing == { - "wall_time": {"h": "0", "min": "0", "s": "2", "ms": "574"}, - "user_time": {"h": "0", "min": "0", "s": "18", "ms": "250"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "320"}, - } - assert self.lobsterout_saveprojection.total_spilling[0] == approx([0.044000000000000004][0]) - assert self.lobsterout_saveprojection.warning_lines == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5.", - "Generally, this is not a critical error. But to help you analyze it,", - "I dumped the band overlap matrices to the file bandOverlaps.lobster.", - "Please check how much they deviate from the identity matrix and decide to", - "use your results only, if you are sure that this is ok.", - ] - - assert self.lobsterout_skipping_all.basis_functions == [ - [ - "3s", - "4s", - "3p_y", - "3p_z", - "3p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ] - ] - assert self.lobsterout_skipping_all.basis_type == ["pbeVaspFit2015"] - assert self.lobsterout_skipping_all.charge_spilling == [0.0268] - assert self.lobsterout_skipping_all.dft_program == "VASP" - assert self.lobsterout_skipping_all.elements == ["Ti"] - assert not self.lobsterout_skipping_all.has_charge - assert not self.lobsterout_skipping_all.has_cohpcar - assert not self.lobsterout_skipping_all.has_coopcar - assert not self.lobsterout_skipping_all.has_doscar - assert not self.lobsterout_skipping_all.has_projection - assert self.lobsterout_skipping_all.has_bandoverlaps - assert not self.lobsterout_skipping_all.has_density_of_energies - assert not self.lobsterout_skipping_all.has_fatbands - assert not self.lobsterout_skipping_all.has_grosspopulation - assert not self.lobsterout_skipping_all.has_cobicar - assert not self.lobsterout_skipping_all.has_madelung - assert self.lobsterout_skipping_all.info_lines == [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 21 and upwards will be ignored.", - ] - assert self.lobsterout_skipping_all.info_orthonormalization == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5." - ] - assert not self.lobsterout_skipping_all.is_restart_from_projection - assert self.lobsterout_skipping_all.lobster_version == "v3.1.0" - assert self.lobsterout_skipping_all.number_of_spins == 1 - assert self.lobsterout_skipping_all.number_of_threads == 8 - assert self.lobsterout_skipping_all.timing == { - "wall_time": {"h": "0", "min": "0", "s": "2", "ms": "117"}, - "user_time": {"h": "0", "min": "0", "s": "16", "ms": "79"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "320"}, - } - assert self.lobsterout_skipping_all.total_spilling[0] == approx([0.044000000000000004][0]) - assert self.lobsterout_skipping_all.warning_lines == [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5.", - "Generally, this is not a critical error. But to help you analyze it,", - "I dumped the band overlap matrices to the file bandOverlaps.lobster.", - "Please check how much they deviate from the identity matrix and decide to", - "use your results only, if you are sure that this is ok.", - ] - - assert self.lobsterout_twospins.basis_functions == [ - [ - "4s", - "4p_y", - "4p_z", - "4p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ] - ] - assert self.lobsterout_twospins.basis_type == ["pbeVaspFit2015"] - assert self.lobsterout_twospins.charge_spilling[0] == approx(0.36619999999999997) - assert self.lobsterout_twospins.charge_spilling[1] == approx(0.36619999999999997) - assert self.lobsterout_twospins.dft_program == "VASP" - assert self.lobsterout_twospins.elements == ["Ti"] - assert self.lobsterout_twospins.has_charge - assert self.lobsterout_twospins.has_cohpcar - assert self.lobsterout_twospins.has_coopcar - assert self.lobsterout_twospins.has_doscar - assert not self.lobsterout_twospins.has_projection - assert self.lobsterout_twospins.has_bandoverlaps - assert not self.lobsterout_twospins.has_density_of_energies - assert not self.lobsterout_twospins.has_fatbands - assert not self.lobsterout_twospins.has_grosspopulation - assert self.lobsterout_twospins.info_lines == [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 19 and upwards will be ignored.", - ] - assert self.lobsterout_twospins.info_orthonormalization == [ - "60 of 294 k-points could not be orthonormalized with an accuracy of 1.0E-5." - ] - assert not self.lobsterout_twospins.is_restart_from_projection - assert self.lobsterout_twospins.lobster_version == "v3.1.0" - assert self.lobsterout_twospins.number_of_spins == 2 - assert self.lobsterout_twospins.number_of_threads == 8 - assert self.lobsterout_twospins.timing == { - "wall_time": {"h": "0", "min": "0", "s": "3", "ms": "71"}, - "user_time": {"h": "0", "min": "0", "s": "22", "ms": "660"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "310"}, - } - assert self.lobsterout_twospins.total_spilling[0] == approx([0.2567][0]) - assert self.lobsterout_twospins.total_spilling[1] == approx([0.2567][0]) - assert self.lobsterout_twospins.warning_lines == [ - "60 of 294 k-points could not be orthonormalized with an accuracy of 1.0E-5.", - "Generally, this is not a critical error. But to help you analyze it,", - "I dumped the band overlap matrices to the file bandOverlaps.lobster.", - "Please check how much they deviate from the identity matrix and decide to", - "use your results only, if you are sure that this is ok.", - ] - - assert self.lobsterout_from_projection.basis_functions == [] - assert self.lobsterout_from_projection.basis_type == [] - assert self.lobsterout_from_projection.charge_spilling[0] == approx(0.0177) - assert self.lobsterout_from_projection.dft_program is None - assert self.lobsterout_from_projection.elements == [] - assert self.lobsterout_from_projection.has_charge - assert self.lobsterout_from_projection.has_cohpcar - assert self.lobsterout_from_projection.has_coopcar - assert self.lobsterout_from_projection.has_doscar - assert not self.lobsterout_from_projection.has_projection - assert not self.lobsterout_from_projection.has_bandoverlaps - assert not self.lobsterout_from_projection.has_density_of_energies - assert not self.lobsterout_from_projection.has_fatbands - assert not self.lobsterout_from_projection.has_grosspopulation - assert self.lobsterout_from_projection.info_lines == [] - assert self.lobsterout_from_projection.info_orthonormalization == [] - assert self.lobsterout_from_projection.is_restart_from_projection - assert self.lobsterout_from_projection.lobster_version == "v3.1.0" - assert self.lobsterout_from_projection.number_of_spins == 1 - assert self.lobsterout_from_projection.number_of_threads == 8 - assert self.lobsterout_from_projection.timing == { - "wall_time": {"h": "0", "min": "2", "s": "1", "ms": "890"}, - "user_time": {"h": "0", "min": "15", "s": "10", "ms": "530"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "400"}, - } - assert self.lobsterout_from_projection.total_spilling[0] == approx([0.1543][0]) - assert self.lobsterout_from_projection.warning_lines == [] - - assert self.lobsterout_GaAs.basis_functions == [ - ["4s", "4p_y", "4p_z", "4p_x"], - [ - "4s", - "4p_y", - "4p_z", - "4p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ], - ] - assert self.lobsterout_GaAs.basis_type == ["Bunge", "Bunge"] - assert self.lobsterout_GaAs.charge_spilling[0] == approx(0.0089) - assert self.lobsterout_GaAs.dft_program == "VASP" - assert self.lobsterout_GaAs.elements == ["As", "Ga"] - assert self.lobsterout_GaAs.has_charge - assert self.lobsterout_GaAs.has_cohpcar - assert self.lobsterout_GaAs.has_coopcar - assert self.lobsterout_GaAs.has_doscar - assert not self.lobsterout_GaAs.has_projection - assert not self.lobsterout_GaAs.has_bandoverlaps - assert not self.lobsterout_GaAs.has_density_of_energies - assert not self.lobsterout_GaAs.has_fatbands - assert not self.lobsterout_GaAs.has_grosspopulation - assert self.lobsterout_GaAs.info_lines == [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 14 and upwards will be ignored.", - ] - assert self.lobsterout_GaAs.info_orthonormalization == [] - assert not self.lobsterout_GaAs.is_restart_from_projection - assert self.lobsterout_GaAs.lobster_version == "v3.1.0" - assert self.lobsterout_GaAs.number_of_spins == 1 - assert self.lobsterout_GaAs.number_of_threads == 8 - assert self.lobsterout_GaAs.timing == { - "wall_time": {"h": "0", "min": "0", "s": "2", "ms": "726"}, - "user_time": {"h": "0", "min": "0", "s": "12", "ms": "370"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "180"}, - } - assert self.lobsterout_GaAs.total_spilling[0] == approx([0.0859][0]) - - assert self.lobsterout_onethread.number_of_threads == 1 - # Test lobsterout of lobster-4.1.0 - assert self.lobsterout_cobi_madelung.has_cobicar - assert self.lobsterout_cobi_madelung.has_cohpcar - assert self.lobsterout_cobi_madelung.has_madelung - assert not self.lobsterout_cobi_madelung.has_doscar_lso - - assert self.lobsterout_doscar_lso.has_doscar_lso - - assert self.lobsterout_skipping_cobi_madelung.has_cobicar is False - assert self.lobsterout_skipping_cobi_madelung.has_madelung is False - - def test_get_doc(self): - comparedict = { - "restart_from_projection": False, - "lobster_version": "v3.1.0", - "threads": 8, - "dft_program": "VASP", - "charge_spilling": [0.0268], - "total_spilling": [0.044000000000000004], - "elements": ["Ti"], - "basis_type": ["pbeVaspFit2015"], - "basis_functions": [ - [ - "3s", - "4s", - "3p_y", - "3p_z", - "3p_x", - "3d_xy", - "3d_yz", - "3d_z^2", - "3d_xz", - "3d_x^2-y^2", - ] - ], - "timing": { - "wall_time": {"h": "0", "min": "0", "s": "2", "ms": "702"}, - "user_time": {"h": "0", "min": "0", "s": "20", "ms": "330"}, - "sys_time": {"h": "0", "min": "0", "s": "0", "ms": "310"}, - }, - "warning_lines": [ - "3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5.", - "Generally, this is not a critical error. But to help you analyze it,", - "I dumped the band overlap matrices to the file bandOverlaps.lobster.", - "Please check how much they deviate from the identity matrix and decide to", - "use your results only, if you are sure that this is ok.", - ], - "info_orthonormalization": ["3 of 147 k-points could not be orthonormalized with an accuracy of 1.0E-5."], - "info_lines": [ - "There are more PAW bands than local basis functions available.", - "To prevent trouble in orthonormalization and Hamiltonian reconstruction", - "the PAW bands from 21 and upwards will be ignored.", - ], - "has_doscar": True, - "has_doscar_lso": False, - "has_cohpcar": True, - "has_coopcar": True, - "has_charge": True, - "has_projection": False, - "has_bandoverlaps": True, - "has_fatbands": False, - "has_grosspopulation": False, - "has_density_of_energies": False, - } - for key, item in self.lobsterout_normal.get_doc().items(): - if key not in ["has_cobicar", "has_madelung"]: - if isinstance(item, str): - assert comparedict[key], item - elif isinstance(item, int): - assert comparedict[key] == item - elif key in ("charge_spilling", "total_spilling"): - assert item[0] == approx(comparedict[key][0]) - elif isinstance(item, (list, dict)): - assert item == comparedict[key] - - -class TestFatband(PymatgenTest): - def setUp(self): - self.fatband_SiO2_p_x = Fatband( - filenames=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x", - Kpointsfile=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/KPOINTS", - vasprun=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/vasprun.xml", - ) - self.vasprun_SiO2_p_x = Vasprun(filename=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/vasprun.xml") - self.bs_symmline = self.vasprun_SiO2_p_x.get_band_structure(line_mode=True, force_hybrid_mode=True) - self.fatband_SiO2_p = Fatband( - filenames=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p", - Kpointsfile=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p/KPOINTS", - vasprun=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p/vasprun.xml", - ) - self.vasprun_SiO2_p = Vasprun(filename=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p/vasprun.xml") - self.bs_symmline2 = self.vasprun_SiO2_p.get_band_structure(line_mode=True, force_hybrid_mode=True) - self.fatband_SiO2_spin = Fatband( - filenames=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_Spin", - Kpointsfile=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_Spin/KPOINTS", - vasprun=os.path.join( - TEST_FILES_DIR, - "cohp", - "Fatband_SiO2/Test_Spin/vasprun.xml", - ), - ) - self.vasprun_SiO2_spin = Vasprun( - filename=os.path.join( - TEST_FILES_DIR, - "cohp", - "Fatband_SiO2/Test_Spin/vasprun.xml", - ) - ) - self.bs_symmline_spin = self.vasprun_SiO2_p.get_band_structure(line_mode=True, force_hybrid_mode=True) - - def test_attributes(self): - assert list(self.fatband_SiO2_p_x.label_dict["M"]) == approx([0.5, 0.0, 0.0]) - assert self.fatband_SiO2_p_x.efermi == self.vasprun_SiO2_p_x.efermi - lattice1 = self.bs_symmline.lattice_rec.as_dict() - lattice2 = self.fatband_SiO2_p_x.lattice.as_dict() - for idx in range(3): - assert lattice1["matrix"][idx] == approx(lattice2["matrix"][idx]) - assert self.fatband_SiO2_p_x.eigenvals[Spin.up][1][1] - self.fatband_SiO2_p_x.efermi == -18.245 - assert self.fatband_SiO2_p_x.is_spinpolarized is False - assert self.fatband_SiO2_p_x.kpoints_array[3] == approx([0.03409091, 0, 0]) - assert self.fatband_SiO2_p_x.nbands == 36 - assert self.fatband_SiO2_p_x.p_eigenvals[Spin.up][2][1]["Si1"]["3p_x"] == 0.002 - assert self.fatband_SiO2_p_x.structure[0].frac_coords == approx([0.0, 0.47634315, 0.666667]) - assert self.fatband_SiO2_p_x.structure[0].species_string == "Si" - assert self.fatband_SiO2_p_x.structure[0].coords == approx([-1.19607309, 2.0716597, 3.67462144]) - - assert list(self.fatband_SiO2_p.label_dict["M"]) == approx([0.5, 0.0, 0.0]) - assert self.fatband_SiO2_p.efermi == self.vasprun_SiO2_p.efermi - lattice1 = self.bs_symmline2.lattice_rec.as_dict() - lattice2 = self.fatband_SiO2_p.lattice.as_dict() - for idx in range(3): - assert lattice1["matrix"][idx] == approx(lattice2["matrix"][idx]) - assert self.fatband_SiO2_p.eigenvals[Spin.up][1][1] - self.fatband_SiO2_p.efermi == -18.245 - assert self.fatband_SiO2_p.is_spinpolarized is False - assert self.fatband_SiO2_p.kpoints_array[3] == approx([0.03409091, 0, 0]) - assert self.fatband_SiO2_p.nbands == 36 - assert self.fatband_SiO2_p.p_eigenvals[Spin.up][2][1]["Si1"]["3p"] == 0.042 - assert self.fatband_SiO2_p.structure[0].frac_coords == approx([0.0, 0.47634315, 0.666667]) - assert self.fatband_SiO2_p.structure[0].species_string == "Si" - assert self.fatband_SiO2_p.structure[0].coords == approx([-1.19607309, 2.0716597, 3.67462144]) - - assert list(self.fatband_SiO2_spin.label_dict["M"]) == approx([0.5, 0.0, 0.0]) - assert self.fatband_SiO2_spin.efermi == self.vasprun_SiO2_spin.efermi - lattice1 = self.bs_symmline_spin.lattice_rec.as_dict() - lattice2 = self.fatband_SiO2_spin.lattice.as_dict() - for idx in range(3): - assert lattice1["matrix"][idx] == approx(lattice2["matrix"][idx]) - assert self.fatband_SiO2_spin.eigenvals[Spin.up][1][1] - self.fatband_SiO2_spin.efermi == -18.245 - assert self.fatband_SiO2_spin.eigenvals[Spin.down][1][1] - self.fatband_SiO2_spin.efermi == -18.245 - assert self.fatband_SiO2_spin.is_spinpolarized - assert self.fatband_SiO2_spin.kpoints_array[3] == approx([0.03409091, 0, 0]) - assert self.fatband_SiO2_spin.nbands == 36 - - assert self.fatband_SiO2_spin.p_eigenvals[Spin.up][2][1]["Si1"]["3p"] == 0.042 - assert self.fatband_SiO2_spin.structure[0].frac_coords == approx([0.0, 0.47634315, 0.666667]) - assert self.fatband_SiO2_spin.structure[0].species_string == "Si" - assert self.fatband_SiO2_spin.structure[0].coords == approx([-1.19607309, 2.0716597, 3.67462144]) - - def test_raises(self): - with pytest.raises( - ValueError, match="The are two FATBAND files for the same atom and orbital. The program will stop" - ): - self.fatband_SiO2_p_x = Fatband( - filenames=[ - f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/FATBAND_si1_3p_x.lobster", - f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/FATBAND_si1_3p_x.lobster", - ], - Kpointsfile=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/KPOINTS", - vasprun=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/vasprun.xml", - ) - - with pytest.raises( - ValueError, - match=r"Make sure all relevant orbitals were generated and that no duplicates \(2p and 2p_x\) are present", - ): - self.fatband_SiO2_p_x = Fatband( - filenames=[ - f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/FATBAND_si1_3p_x.lobster", - f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p/FATBAND_si1_3p.lobster", - ], - Kpointsfile=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/KPOINTS", - vasprun=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/vasprun.xml", - ) - - with pytest.raises(ValueError, match="No FATBAND files in folder or given"): - self.fatband_SiO2_p_x = Fatband( - filenames=".", - Kpointsfile=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/KPOINTS", - vasprun=f"{TEST_FILES_DIR}/cohp/Fatband_SiO2/Test_p_x/vasprun.xml", - ) - - def test_get_bandstructure(self): - bs_p = self.fatband_SiO2_p.get_bandstructure() - atom1 = bs_p.structure[0] - atom2 = self.bs_symmline2.structure[0] - assert atom1.frac_coords[0] == approx(atom2.frac_coords[0]) - assert atom1.frac_coords[1] == approx(atom2.frac_coords[1]) - assert atom1.frac_coords[2] == approx(atom2.frac_coords[2]) - assert atom1.coords[0] == approx(atom2.coords[0]) - assert atom1.coords[1] == approx(atom2.coords[1]) - assert atom1.coords[2] == approx(atom2.coords[2]) - assert atom1.species_string == atom2.species_string - assert bs_p.efermi == self.bs_symmline2.efermi - branch1 = bs_p.branches[0] - branch2 = self.bs_symmline2.branches[0] - assert branch2["name"] == branch1["name"] - assert branch2["start_index"] == branch1["start_index"] - assert branch2["end_index"] == branch1["end_index"] - - assert bs_p.distance[30] == approx(self.bs_symmline2.distance[30]) - lattice1 = bs_p.lattice_rec.as_dict() - lattice2 = self.bs_symmline2.lattice_rec.as_dict() - assert lattice1["matrix"][0] == approx(lattice2["matrix"][0]) - assert lattice1["matrix"][1] == approx(lattice2["matrix"][1]) - assert lattice1["matrix"][2] == approx(lattice2["matrix"][2]) - - assert bs_p.kpoints[8].frac_coords[0] == approx(self.bs_symmline2.kpoints[8].frac_coords[0]) - assert bs_p.kpoints[8].frac_coords[1] == approx(self.bs_symmline2.kpoints[8].frac_coords[1]) - assert bs_p.kpoints[8].frac_coords[2] == approx(self.bs_symmline2.kpoints[8].frac_coords[2]) - assert bs_p.kpoints[8].cart_coords[0] == approx(self.bs_symmline2.kpoints[8].cart_coords[0]) - assert bs_p.kpoints[8].cart_coords[1] == approx(self.bs_symmline2.kpoints[8].cart_coords[1]) - assert bs_p.kpoints[8].cart_coords[2] == approx(self.bs_symmline2.kpoints[8].cart_coords[2]) - assert bs_p.kpoints[50].frac_coords[0] == approx(self.bs_symmline2.kpoints[50].frac_coords[0]) - assert bs_p.kpoints[50].frac_coords[1] == approx(self.bs_symmline2.kpoints[50].frac_coords[1]) - assert bs_p.kpoints[50].frac_coords[2] == approx(self.bs_symmline2.kpoints[50].frac_coords[2]) - assert bs_p.kpoints[50].cart_coords[0] == approx(self.bs_symmline2.kpoints[50].cart_coords[0]) - assert bs_p.kpoints[50].cart_coords[1] == approx(self.bs_symmline2.kpoints[50].cart_coords[1]) - assert bs_p.kpoints[50].cart_coords[2] == approx(self.bs_symmline2.kpoints[50].cart_coords[2]) - assert bs_p.get_band_gap()["energy"] == approx(self.bs_symmline2.get_band_gap()["energy"], abs=1e-2) - assert bs_p.get_projection_on_elements()[Spin.up][0][0]["Si"] == approx(3 * (0.001 + 0.064)) - assert bs_p.get_projections_on_elements_and_orbitals({"Si": ["3p"]})[Spin.up][0][0]["Si"]["3p"] == approx(0.003) - assert bs_p.get_projections_on_elements_and_orbitals({"O": ["2p"]})[Spin.up][0][0]["O"]["2p"] == approx( - 0.002 * 3 + 0.003 * 3 - ) - dict_here = bs_p.get_projections_on_elements_and_orbitals({"Si": ["3s", "3p"], "O": ["2s", "2p"]})[Spin.up][0][ - 0 - ] - assert dict_here["Si"]["3s"] == approx(0.192) - assert dict_here["Si"]["3p"] == approx(0.003) - assert dict_here["O"]["2s"] == approx(0.792) - assert dict_here["O"]["2p"] == approx(0.015) - - bs_spin = self.fatband_SiO2_spin.get_bandstructure() - assert bs_spin.get_projection_on_elements()[Spin.up][0][0]["Si"] == approx(3 * (0.001 + 0.064)) - assert bs_spin.get_projections_on_elements_and_orbitals({"Si": ["3p"]})[Spin.up][0][0]["Si"]["3p"] == approx( - 0.003 - ) - assert bs_spin.get_projections_on_elements_and_orbitals({"O": ["2p"]})[Spin.up][0][0]["O"]["2p"] == approx( - 0.002 * 3 + 0.003 * 3 - ) - dict_here = bs_spin.get_projections_on_elements_and_orbitals({"Si": ["3s", "3p"], "O": ["2s", "2p"]})[Spin.up][ - 0 - ][0] - assert dict_here["Si"]["3s"] == approx(0.192) - assert dict_here["Si"]["3p"] == approx(0.003) - assert dict_here["O"]["2s"] == approx(0.792) - assert dict_here["O"]["2p"] == approx(0.015) - - assert bs_spin.get_projection_on_elements()[Spin.up][0][0]["Si"] == approx(3 * (0.001 + 0.064)) - assert bs_spin.get_projections_on_elements_and_orbitals({"Si": ["3p"]})[Spin.down][0][0]["Si"]["3p"] == approx( - 0.003 - ) - assert bs_spin.get_projections_on_elements_and_orbitals({"O": ["2p"]})[Spin.down][0][0]["O"]["2p"] == approx( - 0.002 * 3 + 0.003 * 3 - ) - dict_here = bs_spin.get_projections_on_elements_and_orbitals({"Si": ["3s", "3p"], "O": ["2s", "2p"]})[ - Spin.down - ][0][0] - assert dict_here["Si"]["3s"] == approx(0.192) - assert dict_here["Si"]["3p"] == approx(0.003) - assert dict_here["O"]["2s"] == approx(0.792) - assert dict_here["O"]["2p"] == approx(0.015) - bs_p_x = self.fatband_SiO2_p_x.get_bandstructure() - assert bs_p_x.get_projection_on_elements()[Spin.up][0][0]["Si"] == approx(3 * (0.001 + 0.064), abs=1e-2) - - -class TestLobsterin(unittest.TestCase): - def setUp(self): - self.Lobsterinfromfile = Lobsterin.from_file(f"{TEST_FILES_DIR}/cohp/lobsterin.1") - self.Lobsterinfromfile2 = Lobsterin.from_file(f"{TEST_FILES_DIR}/cohp/lobsterin.2") - self.Lobsterinfromfile3 = Lobsterin.from_file(f"{TEST_FILES_DIR}/cohp/lobsterin.3") - self.Lobsterinfromfile4 = Lobsterin.from_file(f"{TEST_FILES_DIR}/cohp/lobsterin.4.gz") - - def test_from_file(self): - # test read from file - assert self.Lobsterinfromfile["cohpstartenergy"] == approx(-15.0) - assert self.Lobsterinfromfile["cohpendenergy"] == approx(5.0) - assert self.Lobsterinfromfile["basisset"] == "pbeVaspFit2015" - assert self.Lobsterinfromfile["gaussiansmearingwidth"] == approx(0.1) - assert self.Lobsterinfromfile["basisfunctions"][0] == "Fe 3d 4p 4s" - assert self.Lobsterinfromfile["basisfunctions"][1] == "Co 3d 4p 4s" - assert self.Lobsterinfromfile["skipdos"] - assert self.Lobsterinfromfile["skipcohp"] - assert self.Lobsterinfromfile["skipcoop"] - assert self.Lobsterinfromfile["skippopulationanalysis"] - assert self.Lobsterinfromfile["skipgrosspopulation"] - - # test if comments are correctly removed - assert self.Lobsterinfromfile == self.Lobsterinfromfile2 - - def test_getitem(self): - # tests implementation of getitem, should be case independent - assert self.Lobsterinfromfile["COHPSTARTENERGY"] == approx(-15.0) - - def test_setitem(self): - # test implementation of setitem - self.Lobsterinfromfile["skipCOHP"] = False - assert self.Lobsterinfromfile["skipcohp"] is False - - def test_initialize_from_dict(self): - # initialize from dict - lobsterin = Lobsterin( - { - "cohpstartenergy": -15.0, - "cohpendenergy": 5.0, - "basisset": "pbeVaspFit2015", - "gaussiansmearingwidth": 0.1, - "basisfunctions": ["Fe 3d 4p 4s", "Co 3d 4p 4s"], - "skipdos": True, - "skipcohp": True, - "skipcoop": True, - "skippopulationanalysis": True, - "skipgrosspopulation": True, - } - ) - assert lobsterin["cohpstartenergy"] == approx(-15.0) - assert lobsterin["cohpendenergy"] == approx(5.0) - assert lobsterin["basisset"] == "pbeVaspFit2015" - assert lobsterin["gaussiansmearingwidth"] == approx(0.1) - assert lobsterin["basisfunctions"][0] == "Fe 3d 4p 4s" - assert lobsterin["basisfunctions"][1] == "Co 3d 4p 4s" - assert {*lobsterin} >= {"skipdos", "skipcohp", "skipcoop", "skippopulationanalysis", "skipgrosspopulation"} - with pytest.raises(IOError, match="There are duplicates for the keywords! The program will stop here."): - lobsterin2 = Lobsterin({"cohpstartenergy": -15.0, "cohpstartEnergy": -20.0}) - lobsterin2 = Lobsterin({"cohpstartenergy": -15.0}) - # can only calculate nbands if basis functions are provided - with pytest.raises(IOError, match="No basis functions are provided. The program cannot calculate nbands"): - lobsterin2._get_nbands(structure=Structure.from_file(f"{test_dir_doscar}/POSCAR.Fe3O4")) - - def test_standard_settings(self): - # test standard settings - for option in [ - "standard", - "standard_from_projection", - "standard_with_fatband", - "onlyprojection", - "onlydos", - "onlycohp", - "onlycoop", - "onlycobi", - "onlycohpcoop", - "onlycohpcoopcobi", - ]: - lobsterin1 = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.Fe3O4", - f"{test_dir_doscar}/INCAR.lobster", - f"{test_dir_doscar}/POTCAR.Fe3O4", - option=option, - ) - assert lobsterin1["cohpstartenergy"] == approx(-35.0) - assert lobsterin1["cohpendenergy"] == approx(5.0) - assert lobsterin1["basisset"] == "pbeVaspFit2015" - assert lobsterin1["gaussiansmearingwidth"] == approx(0.1) - assert lobsterin1["basisfunctions"][0] == "Fe 3d 4p 4s " - assert lobsterin1["basisfunctions"][1] == "O 2p 2s " - - if option in [ - "standard", - "standard_with_fatband", - "onlyprojection", - "onlycohp", - "onlycoop", - "onlycohpcoop", - ]: - assert lobsterin1["saveProjectiontoFile"] - if option in [ - "standard", - "standard_with_fatband", - "onlycohp", - "onlycoop", - "onlycohpcoop", - ]: - assert lobsterin1["cohpGenerator"] == "from 0.1 to 6.0 orbitalwise" - if option in ["standard"]: - assert "skipdos" not in lobsterin1 - assert "skipcohp" not in lobsterin1 - assert "skipcoop" not in lobsterin1 - if option in ["standard_with_fatband"]: - assert lobsterin1["createFatband"] == ["Fe 3d 4p 4s ", "O 2p 2s "] - assert "skipdos" not in lobsterin1 - assert "skipcohp" not in lobsterin1 - assert "skipcoop" not in lobsterin1 - if option in ["standard_from_projection"]: - assert lobsterin1["loadProjectionFromFile"], True - if option in [ - "onlyprojection", - "onlycohp", - "onlycoop", - "onlycobi", - "onlycohpcoop", - "onlycohpcoopcobi", - ]: - assert lobsterin1["skipdos"], True - assert lobsterin1["skipPopulationAnalysis"], True - assert lobsterin1["skipGrossPopulation"], True - assert lobsterin1["skipMadelungEnergy"], True - - if option in ["onlydos"]: - assert lobsterin1["skipPopulationAnalysis"], True - assert lobsterin1["skipGrossPopulation"], True - assert lobsterin1["skipcohp"], True - assert lobsterin1["skipcoop"], True - assert lobsterin1["skipcobi"], True - assert lobsterin1["skipMadelungEnergy"], True - if option in ["onlycohp"]: - assert lobsterin1["skipcoop"], True - assert lobsterin1["skipcobi"], True - if option in ["onlycoop"]: - assert lobsterin1["skipcohp"], True - assert lobsterin1["skipcobi"], True - if option in ["onlyprojection"]: - assert lobsterin1["skipdos"], True - if option in ["onlymadelung"]: - assert lobsterin1["skipPopulationAnalysis"], True - assert lobsterin1["skipGrossPopulation"], True - assert lobsterin1["skipcohp"], True - assert lobsterin1["skipcoop"], True - assert lobsterin1["skipcobi"], True - assert lobsterin1["skipdos"], True - # test basis functions by dict - lobsterin_new = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.Fe3O4", - f"{test_dir_doscar}/INCAR.lobster", - dict_for_basis={"Fe": "3d 4p 4s", "O": "2s 2p"}, - option="standard", - ) - assert lobsterin_new["basisfunctions"] == ["Fe 3d 4p 4s", "O 2s 2p"] - - # test gaussian smearing - lobsterin_new = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.Fe3O4", - f"{test_dir_doscar}/INCAR.lobster2", - dict_for_basis={"Fe": "3d 4p 4s", "O": "2s 2p"}, - option="standard", - ) - assert "gaussiansmearingwidth" not in lobsterin_new - - # fatband and ISMEAR=-5 does not work together - with pytest.raises(ValueError, match="ISMEAR has to be 0 for a fatband calculation with Lobster"): - lobsterin_new = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.Fe3O4", - f"{test_dir_doscar}/INCAR.lobster2", - dict_for_basis={"Fe": "3d 4p 4s", "O": "2s 2p"}, - option="standard_with_fatband", - ) - - def test_standard_with_energy_range_from_vasprun(self): - # test standard_with_energy_range_from_vasprun - lobsterin_comp = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.C2.gz", - f"{test_dir_doscar}/INCAR.C2.gz", - f"{test_dir_doscar}/POTCAR.C2.gz", - f"{test_dir_doscar}/vasprun.xml.C2.gz", - option="standard_with_energy_range_from_vasprun", - ) - assert lobsterin_comp["COHPstartEnergy"] == -28.3679 - assert lobsterin_comp["COHPendEnergy"] == 32.8968 - assert lobsterin_comp["COHPSteps"] == 301 - - def test_diff(self): - # test diff - assert self.Lobsterinfromfile.diff(self.Lobsterinfromfile2)["Different"] == {} - assert self.Lobsterinfromfile.diff(self.Lobsterinfromfile2)["Same"]["COHPSTARTENERGY"] == approx(-15.0) - - # test diff in both directions - for entry in self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Same"]: - assert entry in self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Same"] - for entry in self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Same"]: - assert entry in self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Same"] - for entry in self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Different"]: - assert entry in self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Different"] - for entry in self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Different"]: - assert entry in self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Different"] - - assert ( - self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Different"]["SKIPCOHP"]["lobsterin1"] - == self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Different"]["SKIPCOHP"]["lobsterin2"] - ) - - def test_get_basis(self): - # get basis functions - lobsterin1 = Lobsterin({}) - potcar = Potcar.from_file(f"{test_dir_doscar}/POTCAR.Fe3O4") - Potcar_names = [name["symbol"] for name in potcar.spec] - - assert lobsterin1.get_basis( - Structure.from_file(f"{test_dir_doscar}/Fe3O4.cif"), - potcar_symbols=Potcar_names, - ) == ["Fe 3d 4p 4s ", "O 2p 2s "] - potcar = Potcar.from_file(f"{TEST_FILES_DIR}/cohp/POTCAR.GaAs") - Potcar_names = [name["symbol"] for name in potcar.spec] - assert lobsterin1.get_basis( - Structure.from_file(f"{TEST_FILES_DIR}/cohp/POSCAR.GaAs"), - potcar_symbols=Potcar_names, - ) == ["Ga 3d 4p 4s ", "As 4p 4s "] - - def test_get_all_possible_basis_functions(self): - potcar = Potcar.from_file(f"{test_dir_doscar}/POTCAR.Fe3O4") - Potcar_names = [name["symbol"] for name in potcar.spec] - result = Lobsterin.get_all_possible_basis_functions( - Structure.from_file(f"{test_dir_doscar}/Fe3O4.cif"), - potcar_symbols=Potcar_names, - ) - assert result[0] == {"Fe": "3d 4s", "O": "2p 2s"} - assert result[1] == {"Fe": "3d 4s 4p", "O": "2p 2s"} - - potcar2 = Potcar.from_file(f"{test_dir_doscar}/POT_GGA_PAW_PBE_54/POTCAR.Fe_pv.gz") - Potcar_names2 = [name["symbol"] for name in potcar2.spec] - result2 = Lobsterin.get_all_possible_basis_functions( - Structure.from_file(f"{test_dir_doscar}/Fe.cif"), - potcar_symbols=Potcar_names2, - ) - assert result2[0] == {"Fe": "3d 3p 4s"} - - def test_get_potcar_symbols(self): - lobsterin1 = Lobsterin({}) - assert lobsterin1._get_potcar_symbols(f"{test_dir_doscar}/POTCAR.Fe3O4") == ["Fe", "O"] - assert lobsterin1._get_potcar_symbols(f"{TEST_FILES_DIR}/cohp/POTCAR.GaAs") == ["Ga_d", "As"] - - def test_write_lobsterin(self): - # write lobsterin, read it and compare it - outfile_path = tempfile.mkstemp()[1] - lobsterin1 = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.Fe3O4", - f"{test_dir_doscar}/INCAR.lobster", - f"{test_dir_doscar}/POTCAR.Fe3O4", - option="standard", - ) - lobsterin1.write_lobsterin(outfile_path) - lobsterin2 = Lobsterin.from_file(outfile_path) - assert lobsterin1.diff(lobsterin2)["Different"] == {} - - def test_write_incar(self): - # write INCAR and compare - outfile_path = tempfile.mkstemp()[1] - lobsterin1 = Lobsterin.standard_calculations_from_vasp_files( - f"{test_dir_doscar}/POSCAR.Fe3O4", - f"{test_dir_doscar}/INCAR.lobster", - f"{test_dir_doscar}/POTCAR.Fe3O4", - option="standard", - ) - lobsterin1.write_INCAR( - f"{test_dir_doscar}/INCAR.lobster3", - outfile_path, - f"{test_dir_doscar}/POSCAR.Fe3O4", - ) - - incar1 = Incar.from_file(f"{test_dir_doscar}/INCAR.lobster3") - incar2 = Incar.from_file(outfile_path) - - assert incar1.diff(incar2)["Different"] == { - "ISYM": {"INCAR1": 2, "INCAR2": -1}, - "NBANDS": {"INCAR1": None, "INCAR2": 86}, - "NSW": {"INCAR1": 500, "INCAR2": 0}, - "LWAVE": {"INCAR1": False, "INCAR2": True}, - } - - def test_write_kpoints(self): - # line mode - outfile_path = tempfile.mkstemp()[1] - outfile_path2 = tempfile.mkstemp(prefix="POSCAR")[1] - lobsterin1 = Lobsterin({}) - # test writing primitive cell - lobsterin1.write_POSCAR_with_standard_primitive( - POSCAR_input=f"{test_dir_doscar}/POSCAR.Fe3O4", POSCAR_output=outfile_path2 - ) - - lobsterin1.write_KPOINTS( - POSCAR_input=outfile_path2, - KPOINTS_output=outfile_path, - kpoints_line_density=58, - ) - kpoint = Kpoints.from_file(outfile_path) - assert kpoint.num_kpts == 562 - assert kpoint.kpts[-1][0] == approx(-0.5) - assert kpoint.kpts[-1][1] == approx(0.5) - assert kpoint.kpts[-1][2] == approx(0.5) - assert kpoint.labels[-1] == "T" - kpoint2 = Kpoints.from_file(f"{test_dir_doscar}/KPOINTS_band.lobster") - - labels = [] - number = 0 - for label in kpoint.labels: - if label is not None: - if number != 0: - if label != labels[number - 1]: - labels.append(label) - number += 1 - else: - labels.append(label) - number += 1 - - labels2 = [] - number2 = 0 - for label in kpoint2.labels: - if label is not None: - if number2 != 0: - if label != labels2[number2 - 1]: - labels2.append(label) - number2 += 1 - else: - labels2.append(label) - number2 += 1 - assert labels == labels2 - - # without line mode - lobsterin1.write_KPOINTS(POSCAR_input=outfile_path2, KPOINTS_output=outfile_path, line_mode=False) - kpoint = Kpoints.from_file(outfile_path) - kpoint2 = Kpoints.from_file(f"{test_dir_doscar}/IBZKPT.lobster") - - for num_kpt, list_kpoint in enumerate(kpoint.kpts): - assert list_kpoint[0] == approx(kpoint2.kpts[num_kpt][0]) - assert list_kpoint[1] == approx(kpoint2.kpts[num_kpt][1]) - assert list_kpoint[2] == approx(kpoint2.kpts[num_kpt][2]) - - assert kpoint.num_kpts == 108 - - # without line mode, use grid instead of reciprocal density - lobsterin1.write_KPOINTS( - POSCAR_input=outfile_path2, - KPOINTS_output=outfile_path, - line_mode=False, - from_grid=True, - input_grid=[6, 6, 3], - ) - kpoint = Kpoints.from_file(outfile_path) - kpoint2 = Kpoints.from_file(f"{test_dir_doscar}/IBZKPT.lobster") - - for num_kpt, list_kpoint in enumerate(kpoint.kpts): - assert list_kpoint[0] == approx(kpoint2.kpts[num_kpt][0]) - assert list_kpoint[1] == approx(kpoint2.kpts[num_kpt][1]) - assert list_kpoint[2] == approx(kpoint2.kpts[num_kpt][2]) - - assert kpoint.num_kpts == 108 - - # - # #without line mode, using a certain grid, isym=0 instead of -1 - lobsterin1.write_KPOINTS( - POSCAR_input=f"{TEST_FILES_DIR}/cohp/POSCAR.Li", - KPOINTS_output=outfile_path, - line_mode=False, - from_grid=True, - input_grid=[3, 3, 3], - isym=0, - ) - - kpoint1 = Kpoints.from_file(outfile_path) - kpoint2 = Kpoints.from_file(f"{TEST_FILES_DIR}/cohp/IBZKPT_3_3_3_Li") - for ikpoint, kpoint in enumerate(kpoint1.kpts): - assert self.is_kpoint_in_list( - kpoint, - kpoint2.kpts, - kpoint1.kpts_weights[ikpoint], - kpoint2.kpts_weights, - ) - for ikpoint, kpoint in enumerate(kpoint2.kpts): - assert self.is_kpoint_in_list( - kpoint, - kpoint1.kpts, - kpoint2.kpts_weights[ikpoint], - kpoint1.kpts_weights, - ) - - lobsterin1.write_KPOINTS( - POSCAR_input=f"{TEST_FILES_DIR}/cohp/POSCAR.Li", - KPOINTS_output=outfile_path, - line_mode=False, - from_grid=True, - input_grid=[2, 2, 2], - isym=0, - ) - - kpoint1 = Kpoints.from_file(outfile_path) - kpoint2 = Kpoints.from_file(f"{TEST_FILES_DIR}/cohp/IBZKPT_2_2_2_Li") - for ikpoint, kpoint in enumerate(kpoint1.kpts): - assert self.is_kpoint_in_list( - kpoint, - kpoint2.kpts, - kpoint1.kpts_weights[ikpoint], - kpoint2.kpts_weights, - ) - for ikpoint, kpoint in enumerate(kpoint2.kpts): - assert self.is_kpoint_in_list( - kpoint, - kpoint1.kpts, - kpoint2.kpts_weights[ikpoint], - kpoint1.kpts_weights, - ) - - def is_kpoint_in_list(self, kpoint, kpointlist, weight, weightlist) -> bool: - found = 0 - for ikpoint2, kpoint2 in enumerate(kpointlist): - if ( - np.isclose(kpoint[0], kpoint2[0]) - and np.isclose(kpoint[1], kpoint2[1]) - and np.isclose(kpoint[2], kpoint2[2]) - ): - if weight == weightlist[ikpoint2]: - found += 1 - elif ( - np.isclose(-kpoint[0], kpoint2[0]) - and np.isclose(-kpoint[1], kpoint2[1]) - and np.isclose(-kpoint[2], kpoint2[2]) - ) and weight == weightlist[ikpoint2]: - found += 1 - return found == 1 - - def test_msonable_implementation(self): - # tests as dict and from dict methods - newLobsterin = Lobsterin.from_dict(self.Lobsterinfromfile.as_dict()) - assert newLobsterin == self.Lobsterinfromfile - newLobsterin.to_json() - - -class TestBandoverlaps(unittest.TestCase): - def setUp(self): - # test spin polarlized calc and non spinpolarized calc - - self.bandoverlaps1 = Bandoverlaps(f"{TEST_FILES_DIR}/cohp/bandOverlaps.lobster.1") - self.bandoverlaps2 = Bandoverlaps(f"{TEST_FILES_DIR}/cohp/bandOverlaps.lobster.2") - - self.bandoverlaps1_new = Bandoverlaps(f"{TEST_FILES_DIR}/cohp/bandOverlaps.lobster.new.1") - self.bandoverlaps2_new = Bandoverlaps(f"{TEST_FILES_DIR}/cohp/bandOverlaps.lobster.new.2") - - def test_attributes(self): - # bandoverlapsdict - assert self.bandoverlaps1.bandoverlapsdict[Spin.up]["0.5 0 0"]["maxDeviation"] == approx(0.000278953) - assert self.bandoverlaps1_new.bandoverlapsdict[Spin.up]["0 0 0"]["maxDeviation"] == approx(0.0640933) - assert self.bandoverlaps1.bandoverlapsdict[Spin.up]["0.5 0 0"]["matrix"][-1][-1] == approx(0.0188058) - assert self.bandoverlaps1_new.bandoverlapsdict[Spin.up]["0 0 0"]["matrix"][-1][-1] == approx(1.0) - assert self.bandoverlaps1.bandoverlapsdict[Spin.up]["0.5 0 0"]["matrix"][0][0] == approx(1) - assert self.bandoverlaps1_new.bandoverlapsdict[Spin.up]["0 0 0"]["matrix"][0][0] == approx(0.995849) - - assert self.bandoverlaps1.bandoverlapsdict[Spin.down]["0.0261194 0.0261194 0.473881"]["maxDeviation"] == approx( - 4.31567e-05 - ) - assert self.bandoverlaps1_new.bandoverlapsdict[Spin.down]["0 0 0"]["maxDeviation"] == approx(0.064369) - assert self.bandoverlaps1.bandoverlapsdict[Spin.down]["0.0261194 0.0261194 0.473881"]["matrix"][0][ - -1 - ] == approx(4.0066e-07) - assert self.bandoverlaps1_new.bandoverlapsdict[Spin.down]["0 0 0"]["matrix"][0][-1] == approx(1.37447e-09) - - # maxDeviation - assert self.bandoverlaps1.max_deviation[0] == approx(0.000278953) - assert self.bandoverlaps1_new.max_deviation[0] == approx(0.39824) - assert self.bandoverlaps1.max_deviation[-1] == approx(4.31567e-05) - assert self.bandoverlaps1_new.max_deviation[-1] == approx(0.324898) - - assert self.bandoverlaps2.max_deviation[0] == approx(0.000473319) - assert self.bandoverlaps2_new.max_deviation[0] == approx(0.403249) - assert self.bandoverlaps2.max_deviation[-1] == approx(1.48451e-05) - assert self.bandoverlaps2_new.max_deviation[-1] == approx(0.45154) - - def test_has_good_quality(self): - assert not self.bandoverlaps1.has_good_quality_maxDeviation(limit_maxDeviation=0.1) - assert not self.bandoverlaps1_new.has_good_quality_maxDeviation(limit_maxDeviation=0.1) - assert not self.bandoverlaps1.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=9, - number_occ_bands_spin_down=5, - limit_deviation=0.1, - spin_polarized=True, - ) - assert not self.bandoverlaps1_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=9, - number_occ_bands_spin_down=5, - limit_deviation=0.1, - spin_polarized=True, - ) - assert self.bandoverlaps1.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=3, - number_occ_bands_spin_down=0, - limit_deviation=0.001, - spin_polarized=True, - ) - assert self.bandoverlaps1_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=3, - number_occ_bands_spin_down=0, - limit_deviation=0.01, - spin_polarized=True, - ) - assert not self.bandoverlaps1.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, - number_occ_bands_spin_down=1, - limit_deviation=0.000001, - spin_polarized=True, - ) - assert not self.bandoverlaps1_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, - number_occ_bands_spin_down=1, - limit_deviation=0.000001, - spin_polarized=True, - ) - assert not self.bandoverlaps1.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, - number_occ_bands_spin_down=0, - limit_deviation=0.000001, - spin_polarized=True, - ) - assert not self.bandoverlaps1_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, - number_occ_bands_spin_down=0, - limit_deviation=0.000001, - spin_polarized=True, - ) - assert not self.bandoverlaps1.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=0, - number_occ_bands_spin_down=1, - limit_deviation=0.000001, - spin_polarized=True, - ) - assert not self.bandoverlaps1_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=0, - number_occ_bands_spin_down=1, - limit_deviation=0.000001, - spin_polarized=True, - ) - assert not self.bandoverlaps1.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=4, - number_occ_bands_spin_down=4, - limit_deviation=0.001, - spin_polarized=True, - ) - assert not self.bandoverlaps1_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=4, - number_occ_bands_spin_down=4, - limit_deviation=0.001, - spin_polarized=True, - ) - - assert self.bandoverlaps1.has_good_quality_maxDeviation(limit_maxDeviation=100) - assert self.bandoverlaps1_new.has_good_quality_maxDeviation(limit_maxDeviation=100) - assert self.bandoverlaps2.has_good_quality_maxDeviation() - assert not self.bandoverlaps2_new.has_good_quality_maxDeviation() - assert not self.bandoverlaps2.has_good_quality_maxDeviation(limit_maxDeviation=0.0000001) - assert not self.bandoverlaps2_new.has_good_quality_maxDeviation(limit_maxDeviation=0.0000001) - assert not self.bandoverlaps2.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=10, limit_deviation=0.0000001 - ) - assert not self.bandoverlaps2_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=10, limit_deviation=0.0000001 - ) - assert self.bandoverlaps2.has_good_quality_check_occupied_bands(number_occ_bands_spin_up=1, limit_deviation=0.1) - assert self.bandoverlaps2_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, limit_deviation=0.1 - ) - - assert not self.bandoverlaps2.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, limit_deviation=1e-8 - ) - assert not self.bandoverlaps2_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, limit_deviation=1e-8 - ) - assert self.bandoverlaps2.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=10, limit_deviation=0.1 - ) - assert self.bandoverlaps2_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=2, limit_deviation=0.1 - ) - - assert self.bandoverlaps2.has_good_quality_check_occupied_bands(number_occ_bands_spin_up=1, limit_deviation=0.1) - assert self.bandoverlaps2_new.has_good_quality_check_occupied_bands( - number_occ_bands_spin_up=1, limit_deviation=0.1 - ) - - -class TestGrosspop(unittest.TestCase): - def setUp(self): - self.grosspop1 = Grosspop(f"{TEST_FILES_DIR}/cohp/GROSSPOP.lobster") - - def testattributes(self): - assert self.grosspop1.list_dict_grosspop[0]["Mulliken GP"]["3s"] == approx(0.52) - assert self.grosspop1.list_dict_grosspop[0]["Mulliken GP"]["3p_y"] == approx(0.38) - assert self.grosspop1.list_dict_grosspop[0]["Mulliken GP"]["3p_z"] == approx(0.37) - assert self.grosspop1.list_dict_grosspop[0]["Mulliken GP"]["3p_x"] == approx(0.37) - assert self.grosspop1.list_dict_grosspop[0]["Mulliken GP"]["total"] == approx(1.64) - assert self.grosspop1.list_dict_grosspop[0]["element"] == "Si" - assert self.grosspop1.list_dict_grosspop[0]["Loewdin GP"]["3s"] == approx(0.61) - assert self.grosspop1.list_dict_grosspop[0]["Loewdin GP"]["3p_y"] == approx(0.52) - assert self.grosspop1.list_dict_grosspop[0]["Loewdin GP"]["3p_z"] == approx(0.52) - assert self.grosspop1.list_dict_grosspop[0]["Loewdin GP"]["3p_x"] == approx(0.52) - assert self.grosspop1.list_dict_grosspop[0]["Loewdin GP"]["total"] == approx(2.16) - assert self.grosspop1.list_dict_grosspop[5]["Mulliken GP"]["2s"] == approx(1.80) - assert self.grosspop1.list_dict_grosspop[5]["Loewdin GP"]["2s"] == approx(1.60) - assert self.grosspop1.list_dict_grosspop[5]["element"] == "O" - assert self.grosspop1.list_dict_grosspop[8]["Mulliken GP"]["2s"] == approx(1.80) - assert self.grosspop1.list_dict_grosspop[8]["Loewdin GP"]["2s"] == approx(1.60) - assert self.grosspop1.list_dict_grosspop[8]["element"] == "O" - - def test_structure_with_grosspop(self): - struct_dict = { - "@module": "pymatgen.core.structure", - "@class": "Structure", - "charge": None, - "lattice": { - "matrix": [ - [5.021897888834907, 4.53806e-11, 0.0], - [-2.5109484443388332, 4.349090983701526, 0.0], - [0.0, 0.0, 5.511929408565514], - ], - "a": 5.021897888834907, - "b": 5.0218974974248045, - "c": 5.511929408565514, - "alpha": 90.0, - "beta": 90.0, - "gamma": 119.99999598960493, - "volume": 120.38434608659402, - }, - "sites": [ - { - "species": [{"element": "Si", "occu": 1}], - "abc": [-3e-16, 0.4763431475490085, 0.6666669999999968], - "xyz": [-1.1960730853096477, 2.0716596881533986, 3.674621443020128], - "label": "Si", - "properties": {"Total Mulliken GP": 1.64, "Total Loewdin GP": 2.16}, - }, - { - "species": [{"element": "Si", "occu": 1}], - "abc": [0.5236568524509936, 0.5236568524509926, 0.0], - "xyz": [1.3148758827683875, 2.277431295571896, 0.0], - "label": "Si", - "properties": {"Total Mulliken GP": 1.64, "Total Loewdin GP": 2.16}, - }, - { - "species": [{"element": "Si", "occu": 1}], - "abc": [0.4763431475490066, -1.2e-15, 0.3333330000000032], - "xyz": [ - 2.392146647037334, - 2.1611518932482004e-11, - 1.8373079655453863, - ], - "label": "Si", - "properties": {"Total Mulliken GP": 1.64, "Total Loewdin GP": 2.16}, - }, - { - "species": [{"element": "O", "occu": 1}], - "abc": [0.1589037798059321, 0.7440031622164922, 0.4613477252144715], - "xyz": [-1.0701550264153763, 3.235737444648381, 2.5429160941844473], - "label": "O", - "properties": {"Total Mulliken GP": 7.18, "Total Loewdin GP": 6.92}, - }, - { - "species": [{"element": "O", "occu": 1}], - "abc": [0.2559968377835071, 0.4149006175894398, 0.7946807252144676], - "xyz": [0.2437959189219816, 1.8044405351020447, 4.380224059729795], - "label": "O", - "properties": {"Total Mulliken GP": 7.18, "Total Loewdin GP": 6.92}, - }, - { - "species": [{"element": "O", "occu": 1}], - "abc": [0.5850993824105679, 0.8410962201940679, 0.1280147252144683], - "xyz": [0.8263601076506712, 3.6580039876980064, 0.7056081286390611], - "label": "O", - "properties": {"Total Mulliken GP": 7.18, "Total Loewdin GP": 6.92}, - }, - { - "species": [{"element": "O", "occu": 1}], - "abc": [0.7440031622164928, 0.1589037798059326, 0.5386522747855285], - "xyz": [3.337308710918233, 0.6910869960638374, 2.969013314381067], - "label": "O", - "properties": {"Total Mulliken GP": 7.18, "Total Loewdin GP": 6.92}, - }, - { - "species": [{"element": "O", "occu": 1}], - "abc": [0.4149006175894392, 0.2559968377835, 0.2053192747855324], - "xyz": [1.4407936739605638, 1.1133535390791505, 1.13170534883572], - "label": "O", - "properties": {"Total Mulliken GP": 7.18, "Total Loewdin GP": 6.92}, - }, - { - "species": [{"element": "O", "occu": 1}], - "abc": [0.841096220194068, 0.5850993824105675, 0.8719852747855317], - "xyz": [2.754744948452184, 2.5446504486493, 4.806321279926453], - "label": "O", - "properties": {"Total Mulliken GP": 7.18, "Total Loewdin GP": 6.92}, - }, - ], - } - - new_structure = self.grosspop1.get_structure_with_total_grosspop(f"{TEST_FILES_DIR}/cohp/POSCAR.SiO2") - assert_allclose(new_structure.frac_coords, Structure.from_dict(struct_dict).frac_coords) - - -class TestUtils(PymatgenTest): - def test_get_all_possible_basis_combinations(self): - # this basis is just for testing (not correct) - min_basis = ["Li 1s 2s ", "Na 1s 2s", "Si 1s 2s"] - max_basis = ["Li 1s 2p 2s ", "Na 1s 2p 2s", "Si 1s 2s"] - combinations_basis = get_all_possible_basis_combinations(min_basis, max_basis) - assert combinations_basis == [ - ["Li 1s 2s", "Na 1s 2s", "Si 1s 2s"], - ["Li 1s 2s", "Na 1s 2s 2p", "Si 1s 2s"], - ["Li 1s 2s 2p", "Na 1s 2s", "Si 1s 2s"], - ["Li 1s 2s 2p", "Na 1s 2s 2p", "Si 1s 2s"], - ] - - min_basis = ["Li 1s 2s"] - max_basis = ["Li 1s 2s 2p 3s"] - combinations_basis = get_all_possible_basis_combinations(min_basis, max_basis) - assert combinations_basis == [["Li 1s 2s"], ["Li 1s 2s 2p"], ["Li 1s 2s 3s"], ["Li 1s 2s 2p 3s"]] - - min_basis = ["Li 1s 2s", "Na 1s 2s"] - max_basis = ["Li 1s 2s 2p 3s", "Na 1s 2s 2p 3s"] - combinations_basis = get_all_possible_basis_combinations(min_basis, max_basis) - assert combinations_basis == [ - ["Li 1s 2s", "Na 1s 2s"], - ["Li 1s 2s", "Na 1s 2s 2p"], - ["Li 1s 2s", "Na 1s 2s 3s"], - ["Li 1s 2s", "Na 1s 2s 2p 3s"], - ["Li 1s 2s 2p", "Na 1s 2s"], - ["Li 1s 2s 2p", "Na 1s 2s 2p"], - ["Li 1s 2s 2p", "Na 1s 2s 3s"], - ["Li 1s 2s 2p", "Na 1s 2s 2p 3s"], - ["Li 1s 2s 3s", "Na 1s 2s"], - ["Li 1s 2s 3s", "Na 1s 2s 2p"], - ["Li 1s 2s 3s", "Na 1s 2s 3s"], - ["Li 1s 2s 3s", "Na 1s 2s 2p 3s"], - ["Li 1s 2s 2p 3s", "Na 1s 2s"], - ["Li 1s 2s 2p 3s", "Na 1s 2s 2p"], - ["Li 1s 2s 2p 3s", "Na 1s 2s 3s"], - ["Li 1s 2s 2p 3s", "Na 1s 2s 2p 3s"], - ] - - min_basis = ["Si 1s 2s 2p", "Na 1s 2s"] - max_basis = ["Si 1s 2s 2p 3s", "Na 1s 2s 2p 3s"] - combinations_basis = get_all_possible_basis_combinations(min_basis, max_basis) - assert combinations_basis == [ - ["Si 1s 2s 2p", "Na 1s 2s"], - ["Si 1s 2s 2p", "Na 1s 2s 2p"], - ["Si 1s 2s 2p", "Na 1s 2s 3s"], - ["Si 1s 2s 2p", "Na 1s 2s 2p 3s"], - ["Si 1s 2s 2p 3s", "Na 1s 2s"], - ["Si 1s 2s 2p 3s", "Na 1s 2s 2p"], - ["Si 1s 2s 2p 3s", "Na 1s 2s 3s"], - ["Si 1s 2s 2p 3s", "Na 1s 2s 2p 3s"], - ] - - -class TestWavefunction(PymatgenTest): - def test_parse_file(self): - grid, points, real, imaginary, distance = Wavefunction._parse_file( - os.path.join( - test_dir_doscar, - "cohp", - "LCAOWaveFunctionAfterLSO1PlotOfSpin1Kpoint1band1.gz", - ) - ) - assert_array_equal([41, 41, 41], grid) - assert points[4][0] == approx(0.0000) - assert points[4][1] == approx(0.0000) - assert points[4][2] == approx(0.4000) - assert real[8] == approx(1.38863e-01) - assert imaginary[8] == approx(2.89645e-01) - assert len(imaginary) == 41 * 41 * 41 - assert len(real) == 41 * 41 * 41 - assert len(points) == 41 * 41 * 41 - assert distance[0] == approx(0.0000) - - def test_set_volumetric_data(self): - wave1 = Wavefunction( - filename=f"{test_dir_doscar}/cohp/LCAOWaveFunctionAfterLSO1PlotOfSpin1Kpoint1band1.gz", - structure=Structure.from_file(f"{test_dir_doscar}/cohp/POSCAR_O.gz"), - ) - - wave1.set_volumetric_data(grid=wave1.grid, structure=wave1.structure) - assert hasattr(wave1, "volumetricdata_real") - assert hasattr(wave1, "volumetricdata_imaginary") - - def test_get_volumetricdata_real(self): - wave1 = Wavefunction( - filename=os.path.join( - test_dir_doscar, - "cohp", - "LCAOWaveFunctionAfterLSO1PlotOfSpin1Kpoint1band1.gz", - ), - structure=Structure.from_file(f"{test_dir_doscar}/cohp/POSCAR_O.gz"), - ) - volumetricdata_real = wave1.get_volumetricdata_real() - assert volumetricdata_real.data["total"][0, 0, 0] == approx(-3.0966) - - def test_get_volumetricdata_imaginary(self): - wave1 = Wavefunction( - filename=os.path.join( - test_dir_doscar, - "cohp", - "LCAOWaveFunctionAfterLSO1PlotOfSpin1Kpoint1band1.gz", - ), - structure=Structure.from_file(f"{test_dir_doscar}/cohp/POSCAR_O.gz"), - ) - volumetricdata_imaginary = wave1.get_volumetricdata_imaginary() - assert volumetricdata_imaginary.data["total"][0, 0, 0] == approx(-6.45895e00) - - def test_get_volumetricdata_density(self): - wave1 = Wavefunction( - filename=os.path.join( - test_dir_doscar, - "cohp", - "LCAOWaveFunctionAfterLSO1PlotOfSpin1Kpoint1band1.gz", - ), - structure=Structure.from_file(f"{test_dir_doscar}/cohp/POSCAR_O.gz"), - ) - volumetricdata_density = wave1.get_volumetricdata_density() - assert volumetricdata_density.data["total"][0, 0, 0] == approx((-3.0966 * -3.0966) + (-6.45895 * -6.45895)) - - def test_write_file(self): - wave1 = Wavefunction( - filename=os.path.join( - test_dir_doscar, - "cohp", - "LCAOWaveFunctionAfterLSO1PlotOfSpin1Kpoint1band1.gz", - ), - structure=Structure.from_file(f"{test_dir_doscar}/cohp/POSCAR_O.gz"), - ) - wave1.write_file(filename=os.path.join("wavecar_test.vasp"), part="real") - assert os.path.isfile("wavecar_test.vasp") - - wave1.write_file(filename=os.path.join("wavecar_test.vasp"), part="imaginary") - assert os.path.isfile("wavecar_test.vasp") - os.remove("wavecar_test.vasp") - wave1.write_file(filename=os.path.join("density.vasp"), part="density") - assert os.path.isfile("density.vasp") - os.remove("density.vasp") - - -class TestSitePotentials(PymatgenTest): - def setUp(self) -> None: - self.sitepotential = SitePotential(filename=f"{test_dir_doscar}/cohp/SitePotentials.lobster.perovskite") - - def test_attributes(self): - assert self.sitepotential.sitepotentials_Loewdin == [-8.77, -17.08, 9.57, 9.57, 8.45] - assert self.sitepotential.sitepotentials_Mulliken == [-11.38, -19.62, 11.18, 11.18, 10.09] - assert self.sitepotential.madelungenergies_Loewdin == approx(-28.64) - assert self.sitepotential.madelungenergies_Mulliken == approx(-40.02) - assert self.sitepotential.atomlist == ["La1", "Ta2", "N3", "N4", "O5"] - assert self.sitepotential.types == ["La", "Ta", "N", "N", "O"] - assert self.sitepotential.num_atoms == 5 - assert self.sitepotential.ewald_splitting == approx(3.14) - - def test_get_structure(self): - structure = self.sitepotential.get_structure_with_site_potentials(f"{test_dir_doscar}/cohp/POSCAR.perovskite") - assert structure.site_properties["Loewdin Site Potentials (eV)"] == [-8.77, -17.08, 9.57, 9.57, 8.45] - assert structure.site_properties["Mulliken Site Potentials (eV)"] == [-11.38, -19.62, 11.18, 11.18, 10.09] - - -class TestMadelungEnergies(PymatgenTest): - def setUp(self) -> None: - self.madelungenergies = MadelungEnergies(filename=f"{test_dir_doscar}/cohp/MadelungEnergies.lobster.perovskite") - - def test_attributes(self): - assert self.madelungenergies.madelungenergies_Loewdin == approx(-28.64) - assert self.madelungenergies.madelungenergies_Mulliken == approx(-40.02) - assert self.madelungenergies.ewald_splitting == approx(3.14) From 8a4238132e68bb82b373a0b08a114e6809b6a072 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Fri, 22 Sep 2023 15:44:14 +0200 Subject: [PATCH 10/26] get the failing tests to work --- .attach_pid50466 | 0 pymatgen/io/lobster/outputs.py | 6 ++---- 2 files changed, 2 insertions(+), 4 deletions(-) create mode 100644 .attach_pid50466 diff --git a/.attach_pid50466 b/.attach_pid50466 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index d567eedfcc1..bd12433a6f4 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -452,12 +452,10 @@ def __init__(self, filename: str | None = None): if len(data) > 2 and "s]" in str(entry.split()[3:]): self.orbitalwise = True warnings.warn( - "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise " - + "information is not read!" + "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise information is not " + "read!" ) break # condition has only to be met once - else: - self.orbitalwise = False if self.orbitalwise: data_without_orbitals = [] From e0e33768e79891c8fe78122445bdddea2a306ef1 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Fri, 22 Sep 2023 17:22:05 +0200 Subject: [PATCH 11/26] cleaning up merging mess --- .../files}/cohp/NcICOBILIST.lobster | 0 .../files}/cohp/NcICOBILIST.lobster.gz | Bin .../files}/cohp/NcICOBILIST.lobster.nospin | 0 .../cohp/NcICOBILIST.lobster.nospin.withoutorbitals | 0 .../files}/cohp/NcICOBILIST.lobster.withoutorbitals | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {test_files => tests/files}/cohp/NcICOBILIST.lobster (100%) rename {test_files => tests/files}/cohp/NcICOBILIST.lobster.gz (100%) rename {test_files => tests/files}/cohp/NcICOBILIST.lobster.nospin (100%) rename {test_files => tests/files}/cohp/NcICOBILIST.lobster.nospin.withoutorbitals (100%) rename {test_files => tests/files}/cohp/NcICOBILIST.lobster.withoutorbitals (100%) diff --git a/test_files/cohp/NcICOBILIST.lobster b/tests/files/cohp/NcICOBILIST.lobster similarity index 100% rename from test_files/cohp/NcICOBILIST.lobster rename to tests/files/cohp/NcICOBILIST.lobster diff --git a/test_files/cohp/NcICOBILIST.lobster.gz b/tests/files/cohp/NcICOBILIST.lobster.gz similarity index 100% rename from test_files/cohp/NcICOBILIST.lobster.gz rename to tests/files/cohp/NcICOBILIST.lobster.gz diff --git a/test_files/cohp/NcICOBILIST.lobster.nospin b/tests/files/cohp/NcICOBILIST.lobster.nospin similarity index 100% rename from test_files/cohp/NcICOBILIST.lobster.nospin rename to tests/files/cohp/NcICOBILIST.lobster.nospin diff --git a/test_files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals b/tests/files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals similarity index 100% rename from test_files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals rename to tests/files/cohp/NcICOBILIST.lobster.nospin.withoutorbitals diff --git a/test_files/cohp/NcICOBILIST.lobster.withoutorbitals b/tests/files/cohp/NcICOBILIST.lobster.withoutorbitals similarity index 100% rename from test_files/cohp/NcICOBILIST.lobster.withoutorbitals rename to tests/files/cohp/NcICOBILIST.lobster.withoutorbitals From 438c21cc66766f7288fee4c99a8ef64a94234ad4 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Mon, 25 Sep 2023 10:24:16 +0200 Subject: [PATCH 12/26] cleaning up merging --- .attach_pid50466 | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .attach_pid50466 diff --git a/.attach_pid50466 b/.attach_pid50466 deleted file mode 100644 index e69de29bb2d..00000000000 From 97c94c3fbbeb0dac91081fd1e6efb7ddab3dc050 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Sat, 30 Sep 2023 19:29:41 +0200 Subject: [PATCH 13/26] adopted PascalCase for ncicobilist class --- pymatgen/io/lobster/__init__.py | 2 +- pymatgen/io/lobster/outputs.py | 2 +- tests/io/lobster/test_inputs.py | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pymatgen/io/lobster/__init__.py b/pymatgen/io/lobster/__init__.py index 373b694e61a..64af39f0d61 100644 --- a/pymatgen/io/lobster/__init__.py +++ b/pymatgen/io/lobster/__init__.py @@ -18,7 +18,7 @@ Icohplist, Lobsterout, MadelungEnergies, - Ncicobilist, + NciCobiList, SitePotential, Wavefunction, ) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index bd12433a6f4..c1929794e65 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -409,7 +409,7 @@ def icohpcollection(self): return self._icohpcollection -class Ncicobilist: +class NciCobiList: """ Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index b659cac67fa..c732f016675 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -23,7 +23,7 @@ Lobsterin, Lobsterout, MadelungEnergies, - Ncicobilist, + NciCobiList, SitePotential, Wavefunction, ) @@ -672,11 +672,11 @@ def test_values(self): class TestNcicobilist(unittest.TestCase): def setUp(self): - self.ncicobi = Ncicobilist(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster") - self.ncicobigz = Ncicobilist(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.gz") - self.ncicobinospin = Ncicobilist(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin") - self.ncicobinospinwo = Ncicobilist(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin.withoutorbitals") - self.ncicobiwo = Ncicobilist(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.withoutorbitals") + self.ncicobi = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster") + self.ncicobigz = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.gz") + self.ncicobinospin = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin") + self.ncicobinospinwo = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin.withoutorbitals") + self.ncicobiwo = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.withoutorbitals") def test_ncicobilist(self): assert self.ncicobi.is_spin_polarized From 0d6160063954a84e9f8be2455755fbaa60f98ebf Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Sat, 30 Sep 2023 19:32:02 +0200 Subject: [PATCH 14/26] adopted PascalCase for ncicobilist class --- pymatgen/io/lobster/outputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index c1929794e65..e14ed370deb 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -415,7 +415,7 @@ class NciCobiList: Attributes: is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - Ncicobilist (dict): Dict containing the listfile data of the form: + NciCobiList (dict): Dict containing the listfile data of the form: {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, "interaction_type": type of the multi-center interaction @@ -441,7 +441,7 @@ def __init__(self, filename: str | None = None): # If the calculation is spin polarized, the line in the middle # of the file will be another header line. if "spin" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise stuff + # TODO: adapt this for orbitalwise case self.is_spin_polarized = True else: self.is_spin_polarized = False From 6fe669ac0fcea82ca141b3a9a68b22f414d4eee9 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Sat, 30 Sep 2023 19:37:26 +0200 Subject: [PATCH 15/26] adopted PascalCase for ncicobilist class --- tests/io/lobster/test_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index c732f016675..6a1f88d3017 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -670,7 +670,7 @@ def test_values(self): assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["orbitals"]["2s-6s"]["icohp"][Spin.up] == 0.0247 -class TestNcicobilist(unittest.TestCase): +class TestNCicobiList(unittest.TestCase): def setUp(self): self.ncicobi = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster") self.ncicobigz = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.gz") From 90bbec7eb9eb5e7f669cc522f14141ca370e54ac Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Sat, 30 Sep 2023 19:39:48 +0200 Subject: [PATCH 16/26] captitalized the wrong C --- tests/io/lobster/test_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index 6a1f88d3017..7d677ecca8f 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -670,7 +670,7 @@ def test_values(self): assert self.icobi_orbitalwise_spinpolarized.icohplist["2"]["orbitals"]["2s-6s"]["icohp"][Spin.up] == 0.0247 -class TestNCicobiList(unittest.TestCase): +class TestNciCobiList(unittest.TestCase): def setUp(self): self.ncicobi = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster") self.ncicobigz = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.gz") From bfe4d992c7245a32faeb411f0208cafa0814782c Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Sun, 1 Oct 2023 09:29:05 -0700 Subject: [PATCH 17/26] snake_case --- pymatgen/io/lobster/outputs.py | 48 ++++++++++++++++----------------- tests/io/lobster/test_inputs.py | 32 +++++++++++----------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index e14ed370deb..d5b581c5538 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -281,10 +281,10 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s # If the calculation is spin polarized, the line in the middle # of the file will be another header line. - # TODO: adapt this for orbitalwise stuff + # TODO: adapt this for orbital-wise stuff self.is_spin_polarized = "distance" in data[len(data) // 2] - # check if orbitalwise ICOHPLIST + # check if orbital-wise ICOHPLIST # include case when there is only one ICOHP!!! self.orbitalwise = len(data) > 2 and "_" in data[1].split()[1] @@ -301,7 +301,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s data_without_orbitals = data if "distance" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff + # TODO: adapt this for orbital-wise stuff num_bonds = len(data_without_orbitals) // 2 if num_bonds == 0: raise OSError("ICOHPLIST file contains no data.") @@ -433,8 +433,8 @@ def __init__(self, filename: str | None = None): # LOBSTER list files have an extra trailing blank line # and we don't need the header. - with zopen(filename, "rt") as f: - data = f.read().split("\n")[1:-1] + with zopen(filename, "rt") as file: + data = file.read().split("\n")[1:-1] if len(data) == 0: raise OSError("NcICOBILIST file contains no data.") @@ -466,36 +466,36 @@ def __init__(self, filename: str | None = None): data_without_orbitals = data if "spin" in data_without_orbitals[len(data_without_orbitals) // 2]: - # TODO: adapt this for orbitalwise stuff - num_bonds = len(data_without_orbitals) // 2 - if num_bonds == 0: + # TODO: adapt this for orbitalwise case + n_bonds = len(data_without_orbitals) // 2 + if n_bonds == 0: raise OSError("NcICOBILIST file contains no data.") else: - num_bonds = len(data_without_orbitals) + n_bonds = len(data_without_orbitals) self.list_labels = [] - self.list_numofatoms = [] + self.list_n_atoms = [] self.list_ncicobi = [] - self.list_interactiontype = [] + self.list_interaction_type = [] self.list_num = [] - for bond in range(num_bonds): + for bond in range(n_bonds): line = data_without_orbitals[bond].split() ncicobi = {} label = f"{line[0]}" - numofatoms = str(line[1]) + n_atoms = str(line[1]) ncicobi[Spin.up] = float(line[2]) - interactiontype = str(line[3:]).replace("'", "").replace(" ", "") + interaction_type = str(line[3:]).replace("'", "").replace(" ", "") num = 1 if self.is_spin_polarized: - ncicobi[Spin.down] = float(data_without_orbitals[bond + num_bonds + 1].split()[2]) + ncicobi[Spin.down] = float(data_without_orbitals[bond + n_bonds + 1].split()[2]) self.list_labels.append(label) - self.list_numofatoms.append(numofatoms) + self.list_n_atoms.append(n_atoms) self.list_ncicobi.append(ncicobi) - self.list_interactiontype.append(interactiontype) + self.list_interaction_type.append(interaction_type) self.list_num.append(num) # TODO: add functions to get orbital resolved NcICOBIs @@ -505,15 +505,15 @@ def ncicobilist(self) -> dict[Any, dict[str, Any]]: """ Returns: ncicobilist. """ - ncicobilist = {} - for key, _entry in enumerate(self.list_labels): - ncicobilist[str(key + 1)] = { - "number_of_atoms": int(self.list_numofatoms[key]), - "ncicobi": self.list_ncicobi[key], - "interaction_type": self.list_interactiontype[key], + ncicobi_list = {} + for idx in range(len(self.list_labels)): + ncicobi_list[str(idx + 1)] = { + "number_of_atoms": int(self.list_n_atoms[idx]), + "ncicobi": self.list_ncicobi[idx], + "interaction_type": self.list_interaction_type[idx], } - return ncicobilist + return ncicobi_list class Doscar: diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index 7d677ecca8f..c1c44a61508 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -673,30 +673,32 @@ def test_values(self): class TestNciCobiList(unittest.TestCase): def setUp(self): self.ncicobi = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster") - self.ncicobigz = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.gz") - self.ncicobinospin = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin") - self.ncicobinospinwo = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin.withoutorbitals") - self.ncicobiwo = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.withoutorbitals") + self.ncicobi_gz = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.gz") + self.ncicobi_no_spin = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin") + self.ncicobi_no_spin_wo = NciCobiList( + filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.nospin.withoutorbitals" + ) + self.ncicobi_wo = NciCobiList(filename=f"{TEST_FILES_DIR}/cohp/NcICOBILIST.lobster.withoutorbitals") def test_ncicobilist(self): assert self.ncicobi.is_spin_polarized - assert not self.ncicobinospin.is_spin_polarized - assert self.ncicobiwo.is_spin_polarized - assert not self.ncicobinospinwo.is_spin_polarized + assert not self.ncicobi_no_spin.is_spin_polarized + assert self.ncicobi_wo.is_spin_polarized + assert not self.ncicobi_no_spin_wo.is_spin_polarized assert self.ncicobi.orbitalwise - assert self.ncicobinospin.orbitalwise - assert not self.ncicobiwo.orbitalwise - assert not self.ncicobinospinwo.orbitalwise + assert self.ncicobi_no_spin.orbitalwise + assert not self.ncicobi_wo.orbitalwise + assert not self.ncicobi_no_spin_wo.orbitalwise assert len(self.ncicobi.ncicobilist) == 2 assert self.ncicobi.ncicobilist["2"]["number_of_atoms"] == 3 assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == approx(0.00009) assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down] == approx(0.00009) assert self.ncicobi.ncicobilist["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobiwo.ncicobilist["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobigz.ncicobilist["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobigz.ncicobilist["2"]["interaction_type"] - assert sum(list(self.ncicobi.ncicobilist["2"]["ncicobi"].values())) == approx( - self.ncicobinospin.ncicobilist["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobi_wo.ncicobilist["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobi_gz.ncicobilist["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobi_gz.ncicobilist["2"]["interaction_type"] + assert sum(self.ncicobi.ncicobilist["2"]["ncicobi"].values()) == approx( + self.ncicobi_no_spin.ncicobilist["2"]["ncicobi"][Spin.up] ) From 6b7343d65ce0babdafbc8aea69fe595f9a4ac9c5 Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Sun, 1 Oct 2023 09:29:24 -0700 Subject: [PATCH 18/26] use ternary for self.is_spin_polarized --- pymatgen/io/lobster/outputs.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index d5b581c5538..c0d90e99c79 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -438,13 +438,9 @@ def __init__(self, filename: str | None = None): if len(data) == 0: raise OSError("NcICOBILIST file contains no data.") - # If the calculation is spin polarized, the line in the middle + # If the calculation is spin-polarized, the line in the middle # of the file will be another header line. - if "spin" in data[len(data) // 2]: - # TODO: adapt this for orbitalwise case - self.is_spin_polarized = True - else: - self.is_spin_polarized = False + self.is_spin_polarized = "spin" in data[len(data) // 2] # TODO: adapt this for orbitalwise case # check if orbitalwise NcICOBILIST # include case when there is only one NcICOBI From ca017b2f7b4b12e8e98a7c4233eef08db246d228 Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Sun, 1 Oct 2023 09:29:36 -0700 Subject: [PATCH 19/26] fix Attributes indentation --- pymatgen/io/lobster/outputs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index c0d90e99c79..d7cb4565a1a 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -413,12 +413,12 @@ class NciCobiList: """ Class to read NcICOBILIST (multi-center ICOBI) files generated by LOBSTER. - Attributes: - is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. - NciCobiList (dict): Dict containing the listfile data of the form: - {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, - "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, - "interaction_type": type of the multi-center interaction + Attributes: + is_spin_polarized (bool): Boolean to indicate if the calculation is spin polarized. + NciCobiList (dict): Dict containing the listfile data of the form: + {bond: "number_of_atoms": number of atoms involved in the multi-center interaction, + "ncicobi": {Spin.up: Nc-ICOBI(Ef) spin up, Spin.down: ...}}, + "interaction_type": type of the multi-center interaction """ def __init__(self, filename: str | None = None): @@ -448,8 +448,8 @@ def __init__(self, filename: str | None = None): if len(data) > 2 and "s]" in str(entry.split()[3:]): self.orbitalwise = True warnings.warn( - "This is an orbitalwise NcICOBILIST.lobster file. Currently, the orbitalwise information is not " - "read!" + "This is an orbitalwise NcICOBILIST.lobster file. " + "Currently, the orbitalwise information is not read!" ) break # condition has only to be met once From dfb137dcc1e11317d6ef7cca5dab6369e3ae40b0 Mon Sep 17 00:00:00 2001 From: Christina Ertural <52951132+QuantumChemist@users.noreply.github.com> Date: Mon, 2 Oct 2023 23:38:20 +0200 Subject: [PATCH 20/26] commented why filename (NcICOBILIST.lobster) = None for LOBSTER version check COBI features were only implemented in LOBSTER 4.1.0 --- pymatgen/io/lobster/outputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index d7cb4565a1a..b1d9b7fa600 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -427,7 +427,7 @@ def __init__(self, filename: str | None = None): filename: Name of the NcICOBILIST file. """ - if filename is None: + if filename is None: # LOBSTER versions < 4.1.0 don't have any COBI/ICOBI/NcICOBI files at all. warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") filename = "NcICOBILIST.lobster" From 518ab488ecd232fdbddcf4895fa854782a6e7a45 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:41:45 +0000 Subject: [PATCH 21/26] pre-commit auto-fixes --- pymatgen/io/lobster/outputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index b1d9b7fa600..1444ed0bd62 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -427,7 +427,7 @@ def __init__(self, filename: str | None = None): filename: Name of the NcICOBILIST file. """ - if filename is None: # LOBSTER versions < 4.1.0 don't have any COBI/ICOBI/NcICOBI files at all. + if filename is None: # LOBSTER versions < 4.1.0 don't have any COBI/ICOBI/NcICOBI files at all. warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") filename = "NcICOBILIST.lobster" From 1bd53b42a2d1b3c8f858d0454f2eda1b2e6fc758 Mon Sep 17 00:00:00 2001 From: Christina Ertural <52951132+QuantumChemist@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:54:31 +0200 Subject: [PATCH 22/26] Update pymatgen/io/lobster/outputs.py Co-authored-by: J. George Signed-off-by: Christina Ertural <52951132+QuantumChemist@users.noreply.github.com> --- pymatgen/io/lobster/outputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index 1444ed0bd62..00acd6b588a 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -421,7 +421,7 @@ class NciCobiList: "interaction_type": type of the multi-center interaction """ - def __init__(self, filename: str | None = None): + def __init__(self, filename: str | None = "NcICOBILIST.lobster"): """ Args: filename: Name of the NcICOBILIST file. From 25f703b28af0fc582f7bd5adcee9f84a73bc953e Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Wed, 4 Oct 2023 20:16:58 +0200 Subject: [PATCH 23/26] improved the code --- pymatgen/io/lobster/outputs.py | 67 ++++++++++++++++----------------- tests/io/lobster/test_inputs.py | 28 +++++++------- 2 files changed, 46 insertions(+), 49 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index 00acd6b588a..c85a52e16b5 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -108,7 +108,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s spins = [Spin.up, Spin.down] if int(parameters[1]) == 2 else [Spin.up] # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() + data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3:]]).transpose() self.energies = data[0] cohp_data: dict[str, dict[str, Any]] = { "average": { @@ -421,16 +421,12 @@ class NciCobiList: "interaction_type": type of the multi-center interaction """ - def __init__(self, filename: str | None = "NcICOBILIST.lobster"): + def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4.1.0: no COBI/ICOBI/NcICOBI """ Args: filename: Name of the NcICOBILIST file. """ - if filename is None: # LOBSTER versions < 4.1.0 don't have any COBI/ICOBI/NcICOBI files at all. - warnings.warn("Please consider using the newest LOBSTER version (4.1.0+). See http://www.cohp.de/.") - filename = "NcICOBILIST.lobster" - # LOBSTER list files have an extra trailing blank line # and we don't need the header. with zopen(filename, "rt") as file: @@ -446,14 +442,15 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # include case when there is only one NcICOBI for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed if len(data) > 2 and "s]" in str(entry.split()[3:]): - self.orbitalwise = True + self.orbital_wise = True warnings.warn( "This is an orbitalwise NcICOBILIST.lobster file. " "Currently, the orbitalwise information is not read!" ) break # condition has only to be met once + else: self.orbital_wise = False - if self.orbitalwise: + if self.orbital_wise: data_without_orbitals = [] for line in data: if "_" not in str(line.split()[3:]) and "s]" not in str(line.split()[3:]): @@ -497,7 +494,7 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # TODO: add functions to get orbital resolved NcICOBIs @property - def ncicobilist(self) -> dict[Any, dict[str, Any]]: + def ncicobi_list(self) -> dict[Any, dict[str, Any]]: """ Returns: ncicobilist. """ @@ -536,10 +533,10 @@ class Doscar: """ def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str | None = "POSCAR", - structure: IStructure | Structure | None = None, + self, + doscar: str = "DOSCAR.lobster", + structure_file: str | None = "POSCAR", + structure: IStructure | Structure | None = None, ): """ Args: @@ -801,19 +798,19 @@ def __init__(self, filename="lobsterout"): self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data + "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data ) self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data + "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data + and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data ) self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data + "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data + and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data ) self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data + "writing COBICAR.lobster and ICOBILIST.lobster..." in data + and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data ) self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data @@ -823,8 +820,8 @@ def __init__(self, filename="lobsterout"): self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data + "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data + and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data ) def get_doc(self): @@ -1114,7 +1111,7 @@ def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): self.is_spinpolarized = True else: linenumbers = [] - for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): + for iline, line in enumerate(contents[1: self.nbands * 2 + 4]): if line.split()[0] == "#": linenumbers.append(iline) @@ -1196,7 +1193,7 @@ def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): self.p_eigenvals = p_eigenvals label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): + for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts:], start=0): if label is not None: label_dict[label] = kpoints_array[ilabel] @@ -1291,11 +1288,11 @@ def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, + self, + number_occ_bands_spin_up: int, + number_occ_bands_spin_down: int | None = None, + spin_polarized: bool = False, + limit_deviation: float = 0.1, ) -> bool: """ Will check if the deviation from the ideal bandoverlap of all occupied bands @@ -1479,9 +1476,9 @@ def set_volumetric_data(self, grid, structure): if x != Nx and y != Ny and z != Nz: if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) + not np.isclose(self.points[runner][0], x_here, 1e-3) + and not np.isclose(self.points[runner][1], y_here, 1e-3) + and not np.isclose(self.points[runner][2], z_here, 1e-3) ): raise ValueError( "The provided wavefunction from Lobster does not contain all relevant" @@ -1554,9 +1551,9 @@ def write_file(self, filename="WAVECAR.vasp", part="real"): part: which part of the wavefunction will be saved ("real" or "imaginary") """ if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") + hasattr(self, "volumetricdata_real") + and hasattr(self, "volumetricdata_imaginary") + and hasattr(self, "volumetricdata_density") ): self.set_volumetric_data(self.grid, self.structure) if part == "real": diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index 0c12eb2fbb2..fec6c31e2a7 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -685,20 +685,20 @@ def test_ncicobilist(self): assert not self.ncicobi_no_spin.is_spin_polarized assert self.ncicobi_wo.is_spin_polarized assert not self.ncicobi_no_spin_wo.is_spin_polarized - assert self.ncicobi.orbitalwise - assert self.ncicobi_no_spin.orbitalwise - assert not self.ncicobi_wo.orbitalwise - assert not self.ncicobi_no_spin_wo.orbitalwise - assert len(self.ncicobi.ncicobilist) == 2 - assert self.ncicobi.ncicobilist["2"]["number_of_atoms"] == 3 - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == approx(0.00009) - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.down] == approx(0.00009) - assert self.ncicobi.ncicobilist["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobi_wo.ncicobilist["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobilist["2"]["ncicobi"][Spin.up] == self.ncicobi_gz.ncicobilist["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobilist["2"]["interaction_type"] == self.ncicobi_gz.ncicobilist["2"]["interaction_type"] - assert sum(self.ncicobi.ncicobilist["2"]["ncicobi"].values()) == approx( - self.ncicobi_no_spin.ncicobilist["2"]["ncicobi"][Spin.up] + assert self.ncicobi.orbital_wise + assert self.ncicobi_no_spin.orbital_wise + assert not self.ncicobi_wo.orbital_wise + assert not self.ncicobi_no_spin_wo.orbital_wise + assert len(self.ncicobi.ncicobi_list) == 2 + assert self.ncicobi.ncicobi_list["2"]["number_of_atoms"] == 3 + assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == approx(0.00009) + assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.down] == approx(0.00009) + assert self.ncicobi.ncicobi_list["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" + assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_wo.ncicobi_list["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_gz.ncicobi_list["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobi_list["2"]["interaction_type"] == self.ncicobi_gz.ncicobi_list["2"]["interaction_type"] + assert sum(self.ncicobi.ncicobi_list["2"]["ncicobi"].values()) == approx( + self.ncicobi_no_spin.ncicobi_list["2"]["ncicobi"][Spin.up] ) From 1227781c0d3a96b5f0bce2921eec658d8929bf58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:19:10 +0000 Subject: [PATCH 24/26] pre-commit auto-fixes --- pymatgen/io/lobster/outputs.py | 57 +++++++++++++++++---------------- tests/io/lobster/test_inputs.py | 12 +++++-- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index c85a52e16b5..9fa31564c62 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -108,7 +108,7 @@ def __init__(self, are_coops: bool = False, are_cobis: bool = False, filename: s spins = [Spin.up, Spin.down] if int(parameters[1]) == 2 else [Spin.up] # The COHP data start in row num_bonds + 3 - data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3:]]).transpose() + data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() self.energies = data[0] cohp_data: dict[str, dict[str, Any]] = { "average": { @@ -448,7 +448,8 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4 "Currently, the orbitalwise information is not read!" ) break # condition has only to be met once - else: self.orbital_wise = False + else: + self.orbital_wise = False if self.orbital_wise: data_without_orbitals = [] @@ -533,10 +534,10 @@ class Doscar: """ def __init__( - self, - doscar: str = "DOSCAR.lobster", - structure_file: str | None = "POSCAR", - structure: IStructure | Structure | None = None, + self, + doscar: str = "DOSCAR.lobster", + structure_file: str | None = "POSCAR", + structure: IStructure | Structure | None = None, ): """ Args: @@ -798,19 +799,19 @@ def __init__(self, filename="lobsterout"): self.has_doscar = "writing DOSCAR.lobster..." in data and "SKIPPING writing DOSCAR.lobster..." not in data self.has_doscar_lso = ( - "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data + "writing DOSCAR.LSO.lobster..." in data and "SKIPPING writing DOSCAR.LSO.lobster..." not in data ) self.has_cohpcar = ( - "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data - and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data + "writing COOPCAR.lobster and ICOOPLIST.lobster..." in data + and "SKIPPING writing COOPCAR.lobster and ICOOPLIST.lobster..." not in data ) self.has_coopcar = ( - "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data - and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data + "writing COHPCAR.lobster and ICOHPLIST.lobster..." in data + and "SKIPPING writing COHPCAR.lobster and ICOHPLIST.lobster..." not in data ) self.has_cobicar = ( - "writing COBICAR.lobster and ICOBILIST.lobster..." in data - and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data + "writing COBICAR.lobster and ICOBILIST.lobster..." in data + and "SKIPPING writing COBICAR.lobster and ICOBILIST.lobster..." not in data ) self.has_charge = "SKIPPING writing CHARGE.lobster..." not in data @@ -820,8 +821,8 @@ def __init__(self, filename="lobsterout"): self.has_grosspopulation = "writing CHARGE.lobster and GROSSPOP.lobster..." in data self.has_density_of_energies = "writing DensityOfEnergy.lobster..." in data self.has_madelung = ( - "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data - and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data + "writing SitePotentials.lobster and MadelungEnergies.lobster..." in data + and "skipping writing SitePotentials.lobster and MadelungEnergies.lobster..." not in data ) def get_doc(self): @@ -1111,7 +1112,7 @@ def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): self.is_spinpolarized = True else: linenumbers = [] - for iline, line in enumerate(contents[1: self.nbands * 2 + 4]): + for iline, line in enumerate(contents[1 : self.nbands * 2 + 4]): if line.split()[0] == "#": linenumbers.append(iline) @@ -1193,7 +1194,7 @@ def __init__(self, filenames=".", vasprun="vasprun.xml", Kpointsfile="KPOINTS"): self.p_eigenvals = p_eigenvals label_dict = {} - for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts:], start=0): + for ilabel, label in enumerate(kpoints_object.labels[-self.number_kpts :], start=0): if label is not None: label_dict[label] = kpoints_array[ilabel] @@ -1288,11 +1289,11 @@ def has_good_quality_maxDeviation(self, limit_maxDeviation: float = 0.1) -> bool return all(deviation <= limit_maxDeviation for deviation in self.max_deviation) def has_good_quality_check_occupied_bands( - self, - number_occ_bands_spin_up: int, - number_occ_bands_spin_down: int | None = None, - spin_polarized: bool = False, - limit_deviation: float = 0.1, + self, + number_occ_bands_spin_up: int, + number_occ_bands_spin_down: int | None = None, + spin_polarized: bool = False, + limit_deviation: float = 0.1, ) -> bool: """ Will check if the deviation from the ideal bandoverlap of all occupied bands @@ -1476,9 +1477,9 @@ def set_volumetric_data(self, grid, structure): if x != Nx and y != Ny and z != Nz: if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) + not np.isclose(self.points[runner][0], x_here, 1e-3) + and not np.isclose(self.points[runner][1], y_here, 1e-3) + and not np.isclose(self.points[runner][2], z_here, 1e-3) ): raise ValueError( "The provided wavefunction from Lobster does not contain all relevant" @@ -1551,9 +1552,9 @@ def write_file(self, filename="WAVECAR.vasp", part="real"): part: which part of the wavefunction will be saved ("real" or "imaginary") """ if not ( - hasattr(self, "volumetricdata_real") - and hasattr(self, "volumetricdata_imaginary") - and hasattr(self, "volumetricdata_density") + hasattr(self, "volumetricdata_real") + and hasattr(self, "volumetricdata_imaginary") + and hasattr(self, "volumetricdata_density") ): self.set_volumetric_data(self.grid, self.structure) if part == "real": diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index fec6c31e2a7..10889d27df2 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -694,9 +694,15 @@ def test_ncicobilist(self): assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == approx(0.00009) assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.down] == approx(0.00009) assert self.ncicobi.ncicobi_list["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" - assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_wo.ncicobi_list["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_gz.ncicobi_list["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobi_list["2"]["interaction_type"] == self.ncicobi_gz.ncicobi_list["2"]["interaction_type"] + assert ( + self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_wo.ncicobi_list["2"]["ncicobi"][Spin.up] + ) + assert ( + self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_gz.ncicobi_list["2"]["ncicobi"][Spin.up] + ) + assert ( + self.ncicobi.ncicobi_list["2"]["interaction_type"] == self.ncicobi_gz.ncicobi_list["2"]["interaction_type"] + ) assert sum(self.ncicobi.ncicobi_list["2"]["ncicobi"].values()) == approx( self.ncicobi_no_spin.ncicobi_list["2"]["ncicobi"][Spin.up] ) From d2e4df05d9c99aa77d47c2685fa8e88c3297d2a8 Mon Sep 17 00:00:00 2001 From: QuantumChemist Date: Wed, 4 Oct 2023 20:23:35 +0200 Subject: [PATCH 25/26] get the linting check working --- pymatgen/io/lobster/outputs.py | 2 +- tests/io/lobster/test_inputs.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index c85a52e16b5..266fde0fb48 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -440,6 +440,7 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4 # check if orbitalwise NcICOBILIST # include case when there is only one NcICOBI + self.orbital_wise = False # set as default for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed if len(data) > 2 and "s]" in str(entry.split()[3:]): self.orbital_wise = True @@ -448,7 +449,6 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4 "Currently, the orbitalwise information is not read!" ) break # condition has only to be met once - else: self.orbital_wise = False if self.orbital_wise: data_without_orbitals = [] diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index fec6c31e2a7..a15f064cd3e 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -694,9 +694,12 @@ def test_ncicobilist(self): assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == approx(0.00009) assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.down] == approx(0.00009) assert self.ncicobi.ncicobi_list["2"]["interaction_type"] == "[X22[0,0,0]->Xs42[0,0,0]->X31[0,0,0]]" - assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_wo.ncicobi_list["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == self.ncicobi_gz.ncicobi_list["2"]["ncicobi"][Spin.up] - assert self.ncicobi.ncicobi_list["2"]["interaction_type"] == self.ncicobi_gz.ncicobi_list["2"]["interaction_type"] + assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] == \ + self.ncicobi_wo.ncicobi_list["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobi_list["2"]["ncicobi"][Spin.up] \ + == self.ncicobi_gz.ncicobi_list["2"]["ncicobi"][Spin.up] + assert self.ncicobi.ncicobi_list["2"]["interaction_type"] \ + == self.ncicobi_gz.ncicobi_list["2"]["interaction_type"] assert sum(self.ncicobi.ncicobi_list["2"]["ncicobi"].values()) == approx( self.ncicobi_no_spin.ncicobi_list["2"]["ncicobi"][Spin.up] ) From df6c5ba4e0bf40d454ce0ccba5d0471028a3fb2d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:35:51 +0000 Subject: [PATCH 26/26] pre-commit auto-fixes --- pymatgen/io/lobster/outputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index 3e78b0b2f2a..d06b1d3c40c 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -440,7 +440,7 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4 # check if orbitalwise NcICOBILIST # include case when there is only one NcICOBI - self.orbital_wise = False # set as default + self.orbital_wise = False # set as default for entry in data: # NcICOBIs orbitalwise and non-orbitalwise can be mixed if len(data) > 2 and "s]" in str(entry.split()[3:]): self.orbital_wise = True