diffpes 2026.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffpes/__init__.py +65 -0
- diffpes/inout/__init__.py +88 -0
- diffpes/inout/chgcar.py +459 -0
- diffpes/inout/doscar.py +248 -0
- diffpes/inout/eigenval.py +258 -0
- diffpes/inout/hdf5.py +818 -0
- diffpes/inout/helpers.py +295 -0
- diffpes/inout/kpoints.py +433 -0
- diffpes/inout/plotting.py +757 -0
- diffpes/inout/poscar.py +174 -0
- diffpes/inout/procar.py +331 -0
- diffpes/inout/py.typed +0 -0
- diffpes/maths/__init__.py +68 -0
- diffpes/maths/dipole.py +368 -0
- diffpes/maths/gaunt.py +496 -0
- diffpes/maths/spherical_harmonics.py +336 -0
- diffpes/py.typed +0 -0
- diffpes/radial/__init__.py +47 -0
- diffpes/radial/bessel.py +198 -0
- diffpes/radial/integrate.py +128 -0
- diffpes/radial/wavefunctions.py +335 -0
- diffpes/simul/__init__.py +146 -0
- diffpes/simul/broadening.py +258 -0
- diffpes/simul/crosssections.py +196 -0
- diffpes/simul/expanded.py +1021 -0
- diffpes/simul/forward.py +586 -0
- diffpes/simul/oam.py +112 -0
- diffpes/simul/polarization.py +443 -0
- diffpes/simul/py.typed +0 -0
- diffpes/simul/resolution.py +122 -0
- diffpes/simul/self_energy.py +142 -0
- diffpes/simul/spectrum.py +1116 -0
- diffpes/simul/workflow.py +424 -0
- diffpes/tightb/__init__.py +68 -0
- diffpes/tightb/diagonalize.py +340 -0
- diffpes/tightb/hamiltonian.py +286 -0
- diffpes/tightb/projections.py +134 -0
- diffpes/types/__init__.py +162 -0
- diffpes/types/aliases.py +52 -0
- diffpes/types/bands.py +1064 -0
- diffpes/types/dos.py +510 -0
- diffpes/types/geometry.py +306 -0
- diffpes/types/kpath.py +365 -0
- diffpes/types/params.py +496 -0
- diffpes/types/py.typed +0 -0
- diffpes/types/radial_params.py +482 -0
- diffpes/types/self_energy.py +271 -0
- diffpes/types/tb_model.py +531 -0
- diffpes/types/volumetric.py +608 -0
- diffpes/utils/__init__.py +30 -0
- diffpes/utils/math.py +255 -0
- diffpes/utils/py.typed +0 -0
- diffpes-2026.3.1.dist-info/METADATA +176 -0
- diffpes-2026.3.1.dist-info/RECORD +55 -0
- diffpes-2026.3.1.dist-info/WHEEL +4 -0
diffpes/inout/doscar.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""VASP DOSCAR file parser.
|
|
2
|
+
|
|
3
|
+
Extended Summary
|
|
4
|
+
----------------
|
|
5
|
+
Reads VASP DOSCAR files and returns a
|
|
6
|
+
:class:`~diffpes.types.DensityOfStates` or
|
|
7
|
+
:class:`~diffpes.types.FullDensityOfStates` PyTree depending on
|
|
8
|
+
the ``return_mode`` parameter.
|
|
9
|
+
|
|
10
|
+
Routine Listings
|
|
11
|
+
----------------
|
|
12
|
+
:func:`read_doscar`
|
|
13
|
+
Parse a VASP DOSCAR file into a DensityOfStates or
|
|
14
|
+
FullDensityOfStates.
|
|
15
|
+
|
|
16
|
+
Notes
|
|
17
|
+
-----
|
|
18
|
+
Handles both spin-polarized (ISPIN=2) and non-polarized (ISPIN=1)
|
|
19
|
+
DOSCAR formats. The Fermi level is extracted directly from the
|
|
20
|
+
file header.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import jax.numpy as jnp
|
|
26
|
+
import numpy as np
|
|
27
|
+
from beartype.typing import Literal, Optional, Union
|
|
28
|
+
from jaxtyping import Array, Float
|
|
29
|
+
|
|
30
|
+
from diffpes.types import (
|
|
31
|
+
DensityOfStates,
|
|
32
|
+
FullDensityOfStates,
|
|
33
|
+
make_density_of_states,
|
|
34
|
+
make_full_density_of_states,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
_NONSPIN_COLS: int = 3
|
|
38
|
+
_SPIN_COLS: int = 5
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def read_doscar( # noqa: PLR0912, PLR0915
|
|
42
|
+
filename: str = "DOSCAR",
|
|
43
|
+
return_mode: Literal["legacy", "full"] = "legacy",
|
|
44
|
+
) -> Union[DensityOfStates, FullDensityOfStates]:
|
|
45
|
+
"""Parse a VASP DOSCAR file.
|
|
46
|
+
|
|
47
|
+
Reads a VASP DOSCAR file containing total (and optionally
|
|
48
|
+
site-projected) density of states on a uniform energy grid.
|
|
49
|
+
|
|
50
|
+
Extended Summary
|
|
51
|
+
----------------
|
|
52
|
+
The DOSCAR file format written by VASP consists of:
|
|
53
|
+
|
|
54
|
+
* **Header** (6 lines):
|
|
55
|
+
- Line 1: system header with ``NATOMS`` as the first integer.
|
|
56
|
+
- Lines 2-5: additional metadata (unused here).
|
|
57
|
+
- Line 6: ``EMIN EMAX NEDOS EFERMI ...`` -- energy window
|
|
58
|
+
bounds, number of DOS grid points, and the Fermi energy.
|
|
59
|
+
|
|
60
|
+
* **Total DOS block** (``NEDOS`` lines): each line contains the
|
|
61
|
+
energy value followed by density-of-states columns.
|
|
62
|
+
|
|
63
|
+
- ISPIN=1: 3 columns -- ``energy, DOS_up, intDOS_up``.
|
|
64
|
+
- ISPIN=2: 5 columns -- ``energy, DOS_up, DOS_down, intDOS_up,
|
|
65
|
+
intDOS_down``.
|
|
66
|
+
|
|
67
|
+
* **Per-atom PDOS blocks** (optional, ``NATOMS`` blocks of
|
|
68
|
+
``NEDOS`` lines each): each block begins with a header line
|
|
69
|
+
identical in format to line 6 of the main header, followed by
|
|
70
|
+
orbital-projected DOS values. Column counts vary depending on
|
|
71
|
+
``LORBIT`` and spin polarization.
|
|
72
|
+
|
|
73
|
+
Implementation Logic
|
|
74
|
+
--------------------
|
|
75
|
+
1. **Parse header** -- read the first 6 lines. Extract ``NATOMS``
|
|
76
|
+
from line 1 and ``NEDOS`` / ``EFERMI`` from line 6.
|
|
77
|
+
|
|
78
|
+
2. **Read total DOS block** -- peek at the first data line to
|
|
79
|
+
determine the column count (3 for non-spin, 5 for spin).
|
|
80
|
+
Read all ``NEDOS`` rows into a ``(NEDOS, ncols)`` array.
|
|
81
|
+
|
|
82
|
+
3. **Legacy mode early return** -- if ``return_mode == "legacy"``,
|
|
83
|
+
extract only ``energy`` (column 0) and ``total_dos`` (column 1,
|
|
84
|
+
i.e. spin-up DOS) and return a ``DensityOfStates``.
|
|
85
|
+
|
|
86
|
+
4. **Full mode column extraction** -- split columns into spin-up
|
|
87
|
+
DOS, optional spin-down DOS, and integrated DOS arrays.
|
|
88
|
+
|
|
89
|
+
5. **Read PDOS blocks** -- for each of the ``NATOMS`` atoms:
|
|
90
|
+
|
|
91
|
+
a. Read the per-atom header line (EMIN/EMAX/NEDOS/EFERMI).
|
|
92
|
+
b. Read the first data line to determine the PDOS column count.
|
|
93
|
+
c. Read the remaining ``NEDOS - 1`` lines.
|
|
94
|
+
d. Strip the energy column (column 0) and store the orbital
|
|
95
|
+
columns.
|
|
96
|
+
|
|
97
|
+
6. **Stack PDOS** -- if any PDOS blocks were successfully read,
|
|
98
|
+
stack them into a ``(NATOMS, NEDOS, C)`` array where ``C`` is
|
|
99
|
+
the number of orbital columns.
|
|
100
|
+
|
|
101
|
+
7. **Construct return value** -- build a ``FullDensityOfStates``
|
|
102
|
+
PyTree containing all extracted arrays.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
filename : str, optional
|
|
107
|
+
Path to DOSCAR file. Default is ``"DOSCAR"``.
|
|
108
|
+
return_mode : {"legacy", "full"}, optional
|
|
109
|
+
``"legacy"`` (default) returns a ``DensityOfStates`` with
|
|
110
|
+
only spin-up total DOS (backward-compatible). ``"full"``
|
|
111
|
+
returns a ``FullDensityOfStates`` with both spin channels,
|
|
112
|
+
integrated DOS, and PDOS blocks when present.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
dos : DensityOfStates or FullDensityOfStates
|
|
117
|
+
Density of states data.
|
|
118
|
+
|
|
119
|
+
Notes
|
|
120
|
+
-----
|
|
121
|
+
In ``"full"`` mode the parser also reads per-atom PDOS blocks
|
|
122
|
+
that follow the total DOS section. Each atom's PDOS block has
|
|
123
|
+
the same number of energy grid points (``NEDOS``) as the total
|
|
124
|
+
DOS block. The PDOS orbital ordering follows the VASP convention
|
|
125
|
+
determined by ``LORBIT`` (e.g. ``s, p_y, p_z, p_x, d_{xy}, ...``
|
|
126
|
+
for ``LORBIT=11``). The Fermi energy stored in the returned
|
|
127
|
+
object is taken directly from the file header (line 6, column 4).
|
|
128
|
+
"""
|
|
129
|
+
path: Path = Path(filename)
|
|
130
|
+
with path.open("r") as fid:
|
|
131
|
+
header: list[str] = fid.readline().split()
|
|
132
|
+
natoms: int = int(header[0])
|
|
133
|
+
fid.readline()
|
|
134
|
+
fid.readline()
|
|
135
|
+
fid.readline()
|
|
136
|
+
fid.readline()
|
|
137
|
+
meta: list[float] = [float(x) for x in fid.readline().split()]
|
|
138
|
+
nedos: int = int(meta[2])
|
|
139
|
+
efermi: float = meta[3]
|
|
140
|
+
|
|
141
|
+
# Read total DOS block
|
|
142
|
+
first_line: str = fid.readline()
|
|
143
|
+
first_vals: list[float] = [float(x) for x in first_line.split()]
|
|
144
|
+
ncols: int = len(first_vals)
|
|
145
|
+
data: np.ndarray = np.zeros((nedos, ncols), dtype=np.float64)
|
|
146
|
+
data[0, :] = first_vals
|
|
147
|
+
for i in range(1, nedos):
|
|
148
|
+
vals: list[float] = [float(x) for x in fid.readline().split()]
|
|
149
|
+
data[i, :] = vals
|
|
150
|
+
|
|
151
|
+
if return_mode == "legacy":
|
|
152
|
+
energy: Float[Array, " E"] = jnp.asarray(
|
|
153
|
+
data[:, 0], dtype=jnp.float64
|
|
154
|
+
)
|
|
155
|
+
total_dos: Float[Array, " E"] = jnp.asarray(
|
|
156
|
+
data[:, 1], dtype=jnp.float64
|
|
157
|
+
)
|
|
158
|
+
result_legacy: DensityOfStates = make_density_of_states(
|
|
159
|
+
energy=energy,
|
|
160
|
+
total_dos=total_dos,
|
|
161
|
+
fermi_energy=efermi,
|
|
162
|
+
)
|
|
163
|
+
return result_legacy
|
|
164
|
+
|
|
165
|
+
# Full mode: extract all columns
|
|
166
|
+
is_spin: bool = ncols == _SPIN_COLS
|
|
167
|
+
energy_arr: Float[Array, " E"] = jnp.asarray(data[:, 0], dtype=jnp.float64)
|
|
168
|
+
dos_up_arr: Float[Array, " E"] = jnp.asarray(data[:, 1], dtype=jnp.float64)
|
|
169
|
+
dos_down_arr: Optional[Float[Array, " E"]] = None
|
|
170
|
+
int_up_arr: Float[Array, " E"]
|
|
171
|
+
int_down_arr: Optional[Float[Array, " E"]] = None
|
|
172
|
+
|
|
173
|
+
if is_spin:
|
|
174
|
+
dos_down_arr = jnp.asarray(data[:, 2], dtype=jnp.float64)
|
|
175
|
+
int_up_arr = jnp.asarray(data[:, 3], dtype=jnp.float64)
|
|
176
|
+
int_down_arr = jnp.asarray(data[:, 4], dtype=jnp.float64)
|
|
177
|
+
else:
|
|
178
|
+
int_up_arr = jnp.asarray(data[:, 2], dtype=jnp.float64)
|
|
179
|
+
|
|
180
|
+
# Read PDOS blocks if present
|
|
181
|
+
pdos_arr: Optional[Float[Array, "A E C"]] = None
|
|
182
|
+
pdos_blocks: list[np.ndarray] = []
|
|
183
|
+
for _atom in range(natoms):
|
|
184
|
+
# Each PDOS block may have a header line
|
|
185
|
+
# repeating EMIN EMAX NEDOS EFERMI
|
|
186
|
+
# or just start with data lines
|
|
187
|
+
line: str = fid.readline()
|
|
188
|
+
if not line or not line.strip():
|
|
189
|
+
break
|
|
190
|
+
# Check if this is a PDOS header (same format as total DOS header)
|
|
191
|
+
line_vals: list[float] = [float(x) for x in line.split()]
|
|
192
|
+
if _NONSPIN_COLS <= len(line_vals) <= _SPIN_COLS:
|
|
193
|
+
# Could be either a header or short PDOS line
|
|
194
|
+
# PDOS header has same EMIN EMAX NEDOS EFERMI format
|
|
195
|
+
# We detect by checking if first value matches energy range
|
|
196
|
+
# Actually, DOSCAR PDOS blocks always have a header line
|
|
197
|
+
pdos_ncols_check: str = fid.readline()
|
|
198
|
+
if not pdos_ncols_check.strip():
|
|
199
|
+
break
|
|
200
|
+
pdos_first: list[float] = [
|
|
201
|
+
float(x) for x in pdos_ncols_check.split()
|
|
202
|
+
]
|
|
203
|
+
pdos_ncols: int = len(pdos_first)
|
|
204
|
+
atom_data: np.ndarray = np.zeros(
|
|
205
|
+
(nedos, pdos_ncols), dtype=np.float64
|
|
206
|
+
)
|
|
207
|
+
atom_data[0, :] = pdos_first
|
|
208
|
+
for j in range(1, nedos):
|
|
209
|
+
row_line: str = fid.readline()
|
|
210
|
+
if not row_line.strip():
|
|
211
|
+
break
|
|
212
|
+
atom_data[j, :] = [float(x) for x in row_line.split()]
|
|
213
|
+
# Store only the orbital columns (skip energy column 0)
|
|
214
|
+
pdos_blocks.append(atom_data[:, 1:])
|
|
215
|
+
else:
|
|
216
|
+
# This line is the first PDOS data line (no header)
|
|
217
|
+
pdos_ncols = len(line_vals)
|
|
218
|
+
atom_data = np.zeros((nedos, pdos_ncols), dtype=np.float64)
|
|
219
|
+
atom_data[0, :] = line_vals
|
|
220
|
+
for j in range(1, nedos):
|
|
221
|
+
row_line = fid.readline()
|
|
222
|
+
if not row_line.strip():
|
|
223
|
+
break
|
|
224
|
+
atom_data[j, :] = [float(x) for x in row_line.split()]
|
|
225
|
+
pdos_blocks.append(atom_data[:, 1:])
|
|
226
|
+
|
|
227
|
+
if pdos_blocks:
|
|
228
|
+
# Stack into (A, E, C) array
|
|
229
|
+
pdos_arr = jnp.asarray(
|
|
230
|
+
np.stack(pdos_blocks, axis=0), dtype=jnp.float64
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
result_full: FullDensityOfStates = make_full_density_of_states(
|
|
234
|
+
energy=energy_arr,
|
|
235
|
+
total_dos_up=dos_up_arr,
|
|
236
|
+
integrated_dos_up=int_up_arr,
|
|
237
|
+
fermi_energy=efermi,
|
|
238
|
+
total_dos_down=dos_down_arr,
|
|
239
|
+
integrated_dos_down=int_down_arr,
|
|
240
|
+
pdos=pdos_arr,
|
|
241
|
+
natoms=natoms,
|
|
242
|
+
)
|
|
243
|
+
return result_full
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
__all__: list[str] = [
|
|
247
|
+
"read_doscar",
|
|
248
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""VASP EIGENVAL file parser.
|
|
2
|
+
|
|
3
|
+
Extended Summary
|
|
4
|
+
----------------
|
|
5
|
+
Reads VASP EIGENVAL files containing electronic band energies and
|
|
6
|
+
returns a :class:`~diffpes.types.BandStructure` or
|
|
7
|
+
:class:`~diffpes.types.SpinBandStructure` PyTree depending on the
|
|
8
|
+
``return_mode`` parameter.
|
|
9
|
+
|
|
10
|
+
Routine Listings
|
|
11
|
+
----------------
|
|
12
|
+
:func:`read_eigenval`
|
|
13
|
+
Parse a VASP EIGENVAL file into a BandStructure or
|
|
14
|
+
SpinBandStructure.
|
|
15
|
+
|
|
16
|
+
Notes
|
|
17
|
+
-----
|
|
18
|
+
Handles both spin-polarized (ISPIN=2) and non-polarized (ISPIN=1)
|
|
19
|
+
calculations. Bands are sorted by energy within each k-point.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
import jax.numpy as jnp
|
|
25
|
+
import numpy as np
|
|
26
|
+
from beartype.typing import Literal, Optional, Union
|
|
27
|
+
|
|
28
|
+
from diffpes.types import (
|
|
29
|
+
BandStructure,
|
|
30
|
+
SpinBandStructure,
|
|
31
|
+
make_band_structure,
|
|
32
|
+
make_spin_band_structure,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
_ISPIN_SPIN_POLARIZED: int = 2
|
|
36
|
+
_KPOINT_LINE_VALUES: int = 4
|
|
37
|
+
_BAND_LINE_MIN_VALUES: int = 2
|
|
38
|
+
_BAND_LINE_SPIN_VALUES: int = 3
|
|
39
|
+
_EIG_UP_INDEX: int = 1
|
|
40
|
+
_EIG_DOWN_INDEX: int = 2
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def read_eigenval(
|
|
44
|
+
filename: str = "EIGENVAL",
|
|
45
|
+
fermi_energy: float = 0.0,
|
|
46
|
+
return_mode: Literal["legacy", "full"] = "legacy",
|
|
47
|
+
) -> Union[BandStructure, SpinBandStructure]:
|
|
48
|
+
"""Parse a VASP EIGENVAL file.
|
|
49
|
+
|
|
50
|
+
Reads a VASP EIGENVAL file that contains electronic eigenvalues
|
|
51
|
+
(band energies) at each k-point. Supports both ISPIN=1 (non-
|
|
52
|
+
polarized) and ISPIN=2 (spin-polarized) calculations.
|
|
53
|
+
|
|
54
|
+
Extended Summary
|
|
55
|
+
----------------
|
|
56
|
+
The EIGENVAL file format written by VASP consists of:
|
|
57
|
+
|
|
58
|
+
* **Header** (6 lines):
|
|
59
|
+
- Line 1: four integers including ``ISPIN`` as the 4th value.
|
|
60
|
+
``ISPIN=2`` indicates spin-polarized eigenvalues.
|
|
61
|
+
- Lines 2-5: additional metadata (unused here).
|
|
62
|
+
- Line 6: ``NELECT NKPOINTS NBANDS`` -- number of electrons,
|
|
63
|
+
k-points, and bands.
|
|
64
|
+
|
|
65
|
+
* **K-point / band blocks** (``NKPOINTS`` blocks): each block
|
|
66
|
+
starts with a k-point line of 4 floats (``kx ky kz weight``),
|
|
67
|
+
followed by ``NBANDS`` eigenvalue lines.
|
|
68
|
+
|
|
69
|
+
- ISPIN=1: each band line has 2 values -- ``band_index energy``.
|
|
70
|
+
- ISPIN=2: each band line has 3 values --
|
|
71
|
+
``band_index energy_up energy_down``.
|
|
72
|
+
|
|
73
|
+
Implementation Logic
|
|
74
|
+
--------------------
|
|
75
|
+
1. **Parse header** -- read the first 6 lines. Extract ``ISPIN``
|
|
76
|
+
from line 1 (4th integer) and ``NKPOINTS`` / ``NBANDS`` from
|
|
77
|
+
line 6.
|
|
78
|
+
|
|
79
|
+
2. **Allocate arrays** -- create ``(NKPOINTS, 4)`` for k-point
|
|
80
|
+
coordinates+weights and ``(NKPOINTS, NBANDS)`` for spin-up
|
|
81
|
+
eigenvalues. If ``ISPIN=2``, allocate a matching spin-down
|
|
82
|
+
array.
|
|
83
|
+
|
|
84
|
+
3. **Read k-point blocks** -- for each k-point:
|
|
85
|
+
|
|
86
|
+
a. Read the next non-empty line and parse 4 floats for the
|
|
87
|
+
k-point coordinates and weight.
|
|
88
|
+
b. For each band, read the next non-empty line and extract the
|
|
89
|
+
eigenvalue(s). For spin-polarized files, store both spin-up
|
|
90
|
+
(column 1) and spin-down (column 2).
|
|
91
|
+
|
|
92
|
+
4. **Sort eigenvalues** -- sort bands by energy within each
|
|
93
|
+
k-point (ascending) for both spin channels. This ensures
|
|
94
|
+
consistent band ordering even if VASP wrote them unordered.
|
|
95
|
+
|
|
96
|
+
5. **Construct return value**:
|
|
97
|
+
|
|
98
|
+
* ``return_mode="legacy"`` or ISPIN=1: return a
|
|
99
|
+
``BandStructure`` with only spin-up eigenvalues, 3D k-point
|
|
100
|
+
coordinates, and k-point weights.
|
|
101
|
+
* ``return_mode="full"`` with ISPIN=2: return a
|
|
102
|
+
``SpinBandStructure`` containing both spin channels.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
filename : str, optional
|
|
107
|
+
Path to EIGENVAL file. Default is ``"EIGENVAL"``.
|
|
108
|
+
fermi_energy : float, optional
|
|
109
|
+
Fermi level in eV used to reference the eigenvalues.
|
|
110
|
+
Default is 0.0.
|
|
111
|
+
return_mode : {"legacy", "full"}, optional
|
|
112
|
+
``"legacy"`` (default) returns a ``BandStructure`` with only
|
|
113
|
+
spin-up eigenvalues (backward-compatible). ``"full"`` returns
|
|
114
|
+
a ``SpinBandStructure`` with both spin channels when ISPIN=2,
|
|
115
|
+
or a ``BandStructure`` when ISPIN=1.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
bands : BandStructure or SpinBandStructure
|
|
120
|
+
Band structure with eigenvalues and k-points. The type
|
|
121
|
+
depends on ``return_mode`` and the spin polarization.
|
|
122
|
+
|
|
123
|
+
Raises
|
|
124
|
+
------
|
|
125
|
+
ValueError
|
|
126
|
+
If a k-point line has fewer than 4 values, or a band line
|
|
127
|
+
has fewer values than expected for the given ISPIN, or if
|
|
128
|
+
EOF is encountered before all blocks are read.
|
|
129
|
+
|
|
130
|
+
Notes
|
|
131
|
+
-----
|
|
132
|
+
For spin-polarized calculations (ISPIN=2), each eigenvalue line
|
|
133
|
+
contains three columns (index, energy-up, energy-down). In
|
|
134
|
+
``"legacy"`` mode only the spin-up energy is extracted. In
|
|
135
|
+
``"full"`` mode both channels are preserved in a
|
|
136
|
+
``SpinBandStructure``. The Fermi energy is **not** embedded in
|
|
137
|
+
the EIGENVAL file; it must be obtained separately (e.g. from
|
|
138
|
+
DOSCAR or OUTCAR). Eigenvalues are stored as-is from the file
|
|
139
|
+
(not shifted by ``fermi_energy``); the Fermi energy is carried
|
|
140
|
+
alongside the eigenvalues in the returned PyTree for downstream
|
|
141
|
+
consumers to apply the shift if needed.
|
|
142
|
+
"""
|
|
143
|
+
path: Path = Path(filename)
|
|
144
|
+
with path.open("r") as fid:
|
|
145
|
+
header: list[int] = [int(x) for x in fid.readline().split()]
|
|
146
|
+
ispin: int = header[3]
|
|
147
|
+
fid.readline()
|
|
148
|
+
fid.readline()
|
|
149
|
+
fid.readline()
|
|
150
|
+
fid.readline()
|
|
151
|
+
meta: list[int] = [int(x) for x in fid.readline().split()]
|
|
152
|
+
_nelect: int = meta[0]
|
|
153
|
+
nkpoints: int = meta[1]
|
|
154
|
+
nbands: int = meta[2]
|
|
155
|
+
kpoints: np.ndarray = np.zeros((nkpoints, 4), dtype=np.float64)
|
|
156
|
+
eigenvalues_up: np.ndarray = np.zeros(
|
|
157
|
+
(nkpoints, nbands), dtype=np.float64
|
|
158
|
+
)
|
|
159
|
+
eigenvalues_down: Optional[np.ndarray] = None
|
|
160
|
+
if ispin == _ISPIN_SPIN_POLARIZED:
|
|
161
|
+
eigenvalues_down = np.zeros((nkpoints, nbands), dtype=np.float64)
|
|
162
|
+
for k in range(nkpoints):
|
|
163
|
+
kpoint_line: str = _read_next_nonempty_line(fid)
|
|
164
|
+
if not kpoint_line:
|
|
165
|
+
msg = "Unexpected EOF while reading EIGENVAL k-point block."
|
|
166
|
+
raise ValueError(msg)
|
|
167
|
+
kpoint_vals: list[float] = [float(x) for x in kpoint_line.split()]
|
|
168
|
+
if len(kpoint_vals) < _KPOINT_LINE_VALUES:
|
|
169
|
+
msg = "Invalid EIGENVAL k-point line; expected 4 values."
|
|
170
|
+
raise ValueError(msg)
|
|
171
|
+
kpoints[k, :] = kpoint_vals[:_KPOINT_LINE_VALUES]
|
|
172
|
+
for b in range(nbands):
|
|
173
|
+
band_line: str = _read_next_nonempty_line(fid)
|
|
174
|
+
if not band_line:
|
|
175
|
+
msg = "Unexpected EOF while reading EIGENVAL band line."
|
|
176
|
+
raise ValueError(msg)
|
|
177
|
+
vals: list[float] = [float(x) for x in band_line.split()]
|
|
178
|
+
if len(vals) < _BAND_LINE_MIN_VALUES:
|
|
179
|
+
msg = "Invalid EIGENVAL band line; expected band energy."
|
|
180
|
+
raise ValueError(msg)
|
|
181
|
+
eigenvalues_up[k, b] = vals[_EIG_UP_INDEX]
|
|
182
|
+
if (
|
|
183
|
+
ispin == _ISPIN_SPIN_POLARIZED
|
|
184
|
+
and eigenvalues_down is not None
|
|
185
|
+
):
|
|
186
|
+
if len(vals) < _BAND_LINE_SPIN_VALUES:
|
|
187
|
+
msg = (
|
|
188
|
+
"Invalid spin-polarized EIGENVAL band line; "
|
|
189
|
+
"expected spin-down energy."
|
|
190
|
+
)
|
|
191
|
+
raise ValueError(msg)
|
|
192
|
+
eigenvalues_down[k, b] = vals[_EIG_DOWN_INDEX]
|
|
193
|
+
eigenvalues_up = np.sort(eigenvalues_up, axis=1)
|
|
194
|
+
if eigenvalues_down is not None:
|
|
195
|
+
eigenvalues_down = np.sort(eigenvalues_down, axis=1)
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
return_mode == "full"
|
|
199
|
+
and ispin == _ISPIN_SPIN_POLARIZED
|
|
200
|
+
and eigenvalues_down is not None
|
|
201
|
+
):
|
|
202
|
+
return make_spin_band_structure(
|
|
203
|
+
eigenvalues_up=jnp.asarray(eigenvalues_up),
|
|
204
|
+
eigenvalues_down=jnp.asarray(eigenvalues_down),
|
|
205
|
+
kpoints=jnp.asarray(kpoints[:, :3]),
|
|
206
|
+
kpoint_weights=jnp.asarray(kpoints[:, 3]),
|
|
207
|
+
fermi_energy=fermi_energy,
|
|
208
|
+
)
|
|
209
|
+
bands: BandStructure = make_band_structure(
|
|
210
|
+
eigenvalues=jnp.asarray(eigenvalues_up),
|
|
211
|
+
kpoints=jnp.asarray(kpoints[:, :3]),
|
|
212
|
+
kpoint_weights=jnp.asarray(kpoints[:, 3]),
|
|
213
|
+
fermi_energy=fermi_energy,
|
|
214
|
+
)
|
|
215
|
+
return bands
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _read_next_nonempty_line(fid) -> str: # noqa: ANN001
|
|
219
|
+
"""Read and return the next non-empty line, or ``""`` at EOF.
|
|
220
|
+
|
|
221
|
+
Extended Summary
|
|
222
|
+
----------------
|
|
223
|
+
Helper that skips blank lines in the EIGENVAL file. VASP separates
|
|
224
|
+
k-point blocks with empty lines; this function transparently
|
|
225
|
+
consumes them so that the main parsing loop does not need to handle
|
|
226
|
+
blank-line logic.
|
|
227
|
+
|
|
228
|
+
Implementation Logic
|
|
229
|
+
--------------------
|
|
230
|
+
1. Call ``fid.readline()`` in a loop.
|
|
231
|
+
2. If the returned string is falsy (empty string ``""``), EOF has
|
|
232
|
+
been reached -- return ``""``.
|
|
233
|
+
3. If the stripped line is non-empty, return the original
|
|
234
|
+
(unstripped) line.
|
|
235
|
+
4. Otherwise continue to the next line.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
fid : file-like
|
|
240
|
+
Open file handle positioned somewhere within the EIGENVAL file.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
line : str
|
|
245
|
+
The next non-empty line (with trailing newline), or ``""`` if
|
|
246
|
+
the end of the file is reached.
|
|
247
|
+
"""
|
|
248
|
+
while True:
|
|
249
|
+
line: str = fid.readline()
|
|
250
|
+
if not line:
|
|
251
|
+
return ""
|
|
252
|
+
if line.strip():
|
|
253
|
+
return line
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
__all__: list[str] = [
|
|
257
|
+
"read_eigenval",
|
|
258
|
+
]
|