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.
Files changed (55) hide show
  1. diffpes/__init__.py +65 -0
  2. diffpes/inout/__init__.py +88 -0
  3. diffpes/inout/chgcar.py +459 -0
  4. diffpes/inout/doscar.py +248 -0
  5. diffpes/inout/eigenval.py +258 -0
  6. diffpes/inout/hdf5.py +818 -0
  7. diffpes/inout/helpers.py +295 -0
  8. diffpes/inout/kpoints.py +433 -0
  9. diffpes/inout/plotting.py +757 -0
  10. diffpes/inout/poscar.py +174 -0
  11. diffpes/inout/procar.py +331 -0
  12. diffpes/inout/py.typed +0 -0
  13. diffpes/maths/__init__.py +68 -0
  14. diffpes/maths/dipole.py +368 -0
  15. diffpes/maths/gaunt.py +496 -0
  16. diffpes/maths/spherical_harmonics.py +336 -0
  17. diffpes/py.typed +0 -0
  18. diffpes/radial/__init__.py +47 -0
  19. diffpes/radial/bessel.py +198 -0
  20. diffpes/radial/integrate.py +128 -0
  21. diffpes/radial/wavefunctions.py +335 -0
  22. diffpes/simul/__init__.py +146 -0
  23. diffpes/simul/broadening.py +258 -0
  24. diffpes/simul/crosssections.py +196 -0
  25. diffpes/simul/expanded.py +1021 -0
  26. diffpes/simul/forward.py +586 -0
  27. diffpes/simul/oam.py +112 -0
  28. diffpes/simul/polarization.py +443 -0
  29. diffpes/simul/py.typed +0 -0
  30. diffpes/simul/resolution.py +122 -0
  31. diffpes/simul/self_energy.py +142 -0
  32. diffpes/simul/spectrum.py +1116 -0
  33. diffpes/simul/workflow.py +424 -0
  34. diffpes/tightb/__init__.py +68 -0
  35. diffpes/tightb/diagonalize.py +340 -0
  36. diffpes/tightb/hamiltonian.py +286 -0
  37. diffpes/tightb/projections.py +134 -0
  38. diffpes/types/__init__.py +162 -0
  39. diffpes/types/aliases.py +52 -0
  40. diffpes/types/bands.py +1064 -0
  41. diffpes/types/dos.py +510 -0
  42. diffpes/types/geometry.py +306 -0
  43. diffpes/types/kpath.py +365 -0
  44. diffpes/types/params.py +496 -0
  45. diffpes/types/py.typed +0 -0
  46. diffpes/types/radial_params.py +482 -0
  47. diffpes/types/self_energy.py +271 -0
  48. diffpes/types/tb_model.py +531 -0
  49. diffpes/types/volumetric.py +608 -0
  50. diffpes/utils/__init__.py +30 -0
  51. diffpes/utils/math.py +255 -0
  52. diffpes/utils/py.typed +0 -0
  53. diffpes-2026.3.1.dist-info/METADATA +176 -0
  54. diffpes-2026.3.1.dist-info/RECORD +55 -0
  55. diffpes-2026.3.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,433 @@
1
+ """VASP KPOINTS file parser.
2
+
3
+ Extended Summary
4
+ ----------------
5
+ Reads VASP KPOINTS files and returns a
6
+ :class:`~diffpes.types.KPathInfo` PyTree containing plotting labels and
7
+ mode-specific metadata (automatic grid/shift, explicit weights, and
8
+ line-mode segment endpoints).
9
+
10
+ Routine Listings
11
+ ----------------
12
+ :func:`read_kpoints`
13
+ Parse a VASP KPOINTS file into KPathInfo.
14
+ """
15
+
16
+ import re
17
+ from pathlib import Path
18
+
19
+ import jax.numpy as jnp
20
+ from beartype.typing import Optional
21
+
22
+ from diffpes.types import KPathInfo, make_kpath_info
23
+
24
+ _FLOAT_TOKEN_RE: re.Pattern[str] = re.compile(
25
+ r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?"
26
+ )
27
+ _XYZ_COMPONENTS: int = 3
28
+ _WEIGHT_COMPONENT_INDEX: int = 3
29
+ _WEIGHT_COMPONENT_COUNT: int = 4
30
+ _COORDINATE_MODE_TOKENS: set[str] = {
31
+ "cartesian",
32
+ "reciprocal",
33
+ "direct",
34
+ "fractional",
35
+ }
36
+
37
+
38
+ def read_kpoints( # noqa: PLR0915
39
+ filename: str = "KPOINTS",
40
+ ) -> KPathInfo:
41
+ """Parse a VASP KPOINTS file.
42
+
43
+ Reads a VASP KPOINTS file that specifies Brillouin-zone sampling.
44
+ Supports the three standard modes:
45
+ Line-mode (path segments), Automatic (Monkhorst-Pack/Gamma grids),
46
+ and Explicit (listed k-points with optional weights).
47
+
48
+ Implementation Logic
49
+ --------------------
50
+ 1. Parse comment, line-2 integer, and line-3 mode/scheme.
51
+ 2. Dispatch by mode:
52
+ - Line-mode: parse paired endpoint lines, derive segment count,
53
+ endpoint k-points, labels, and label indices.
54
+ - Automatic: parse grid and shift vectors.
55
+ - Explicit: parse listed k-points and weights.
56
+ 3. Construct ``KPathInfo`` with both legacy plotting fields and
57
+ richer mode-specific metadata.
58
+
59
+ Parameters
60
+ ----------
61
+ filename : str, optional
62
+ Path to KPOINTS file. Default is ``"KPOINTS"``.
63
+
64
+ Returns
65
+ -------
66
+ kpath : KPathInfo
67
+ K-point metadata including labels/indices and mode-specific
68
+ parsed fields.
69
+
70
+ Notes
71
+ -----
72
+ In Line-mode, line 2 is points-per-segment. ``num_kpoints`` in the
73
+ returned object is the total count ``segments * points_per_segment``,
74
+ preserving existing plotting behavior.
75
+ """
76
+ path: Path = Path(filename)
77
+ with path.open("r") as fid:
78
+ comment: str = fid.readline().strip()
79
+ num_line: str = fid.readline().strip()
80
+ points_per_segment: int = int(num_line.split(maxsplit=1)[0])
81
+ scheme_or_mode: str = fid.readline().strip()
82
+ mode_line: str = scheme_or_mode.lower()
83
+
84
+ labels: list[str] = []
85
+ label_indices: list[int] = []
86
+ line_endpoints: list[list[float]] = []
87
+ explicit_kpoints: list[list[float]] = []
88
+ explicit_weights: list[float] = []
89
+ grid: Optional[list[int]] = None
90
+ shift: Optional[list[float]] = None
91
+ coord_mode: str = ""
92
+ segments: int = 0
93
+ total_kpts: int
94
+
95
+ if "line" in mode_line:
96
+ mode: str = "Line-mode"
97
+ coord_mode = fid.readline().strip()
98
+ raw_lines: list[str] = [
99
+ line.strip() for line in fid if line.strip()
100
+ ]
101
+ segments = len(raw_lines) // 2
102
+
103
+ if segments > 0:
104
+ line_endpoints.append(_extract_coords(raw_lines[0]))
105
+ labels.append(_extract_label(raw_lines[0]))
106
+ label_indices.append(0)
107
+
108
+ idx: int = 0
109
+ for seg in range(segments):
110
+ end_line: str = raw_lines[2 * seg + 1]
111
+ line_endpoints.append(_extract_coords(end_line))
112
+ labels.append(_extract_label(end_line))
113
+ idx += points_per_segment
114
+ label_indices.append(idx - 1)
115
+
116
+ total_kpts = segments * points_per_segment
117
+ elif points_per_segment == 0:
118
+ mode = "Automatic"
119
+ coord_mode = scheme_or_mode
120
+ grid = _parse_grid(fid.readline())
121
+ shift = _parse_shift(fid.readline())
122
+ total_kpts = 0
123
+ else:
124
+ mode = "Explicit"
125
+ remaining_lines: list[str] = [
126
+ line.strip() for line in fid if line.strip()
127
+ ]
128
+ coord_mode = scheme_or_mode
129
+ if (
130
+ mode_line not in _COORDINATE_MODE_TOKENS
131
+ and remaining_lines
132
+ and not _looks_like_kpoint_line(remaining_lines[0])
133
+ ):
134
+ coord_mode = remaining_lines.pop(0)
135
+ explicit_kpoints, explicit_weights = _parse_explicit_kpoints(
136
+ remaining_lines, points_per_segment
137
+ )
138
+ total_kpts = points_per_segment
139
+
140
+ line_endpoints_arr: Optional[jnp.ndarray] = None
141
+ if line_endpoints:
142
+ line_endpoints_arr = jnp.asarray(line_endpoints, dtype=jnp.float64)
143
+ explicit_kpoints_arr: Optional[jnp.ndarray] = None
144
+ if explicit_kpoints:
145
+ explicit_kpoints_arr = jnp.asarray(explicit_kpoints, dtype=jnp.float64)
146
+ explicit_weights_arr: Optional[jnp.ndarray] = None
147
+ if explicit_weights:
148
+ explicit_weights_arr = jnp.asarray(explicit_weights, dtype=jnp.float64)
149
+ grid_arr: Optional[jnp.ndarray] = None
150
+ if grid is not None:
151
+ grid_arr = jnp.asarray(grid, dtype=jnp.int32)
152
+ shift_arr: Optional[jnp.ndarray] = None
153
+ if shift is not None:
154
+ shift_arr = jnp.asarray(shift, dtype=jnp.float64)
155
+
156
+ parsed_kpoints: Optional[jnp.ndarray] = line_endpoints_arr
157
+ parsed_weights: Optional[jnp.ndarray] = None
158
+ if mode == "Explicit":
159
+ parsed_kpoints = explicit_kpoints_arr
160
+ parsed_weights = explicit_weights_arr
161
+
162
+ kpath: KPathInfo = make_kpath_info(
163
+ num_kpoints=total_kpts,
164
+ label_indices=label_indices if label_indices else [0],
165
+ points_per_segment=points_per_segment,
166
+ segments=segments,
167
+ kpoints=parsed_kpoints,
168
+ weights=parsed_weights,
169
+ grid=grid_arr,
170
+ shift=shift_arr,
171
+ mode=mode,
172
+ labels=tuple(labels),
173
+ comment=comment,
174
+ coordinate_mode=coord_mode,
175
+ )
176
+ return kpath
177
+
178
+
179
+ def _parse_explicit_kpoints(
180
+ lines: list[str],
181
+ num_kpoints: int,
182
+ ) -> tuple[list[list[float]], list[float]]:
183
+ """Parse explicit-mode k-point coordinates and optional weights.
184
+
185
+ Extended Summary
186
+ ----------------
187
+ In VASP explicit KPOINTS mode, each k-point is listed on its own
188
+ line with at least three fractional/Cartesian coordinates and an
189
+ optional fourth column for the integration weight.
190
+
191
+ Implementation Logic
192
+ --------------------
193
+ 1. Iterate over ``lines``, stopping once ``num_kpoints`` k-points
194
+ have been collected.
195
+ 2. Split each line and parse all tokens as floats. Raise
196
+ ``ValueError`` if any token is not numeric or if fewer than 3
197
+ values are present.
198
+ 3. Store the first 3 values as coordinates.
199
+ 4. If a 4th value is present, use it as the weight; otherwise
200
+ default to 1.0.
201
+
202
+ Parameters
203
+ ----------
204
+ lines : list[str]
205
+ Remaining lines from the KPOINTS file after the mode line.
206
+ num_kpoints : int
207
+ Expected number of k-points to parse.
208
+
209
+ Returns
210
+ -------
211
+ points : list[list[float]]
212
+ Parsed k-point coordinates, each a 3-element list.
213
+ weights : list[float]
214
+ Corresponding k-point weights.
215
+
216
+ Raises
217
+ ------
218
+ ValueError
219
+ If a coordinate line cannot be parsed or has fewer than 3
220
+ numeric tokens.
221
+ """
222
+ points: list[list[float]] = []
223
+ weights: list[float] = []
224
+ for stripped in lines:
225
+ if len(points) >= num_kpoints:
226
+ break
227
+ parts: list[float]
228
+ try:
229
+ parts = [float(x) for x in stripped.split()]
230
+ except ValueError as exc:
231
+ msg = f"Invalid explicit KPOINTS coordinate line: {stripped!r}"
232
+ raise ValueError(msg) from exc
233
+ if len(parts) < _XYZ_COMPONENTS:
234
+ msg = "Explicit KPOINTS line must contain at least 3 coordinates."
235
+ raise ValueError(msg)
236
+ points.append(parts[:_XYZ_COMPONENTS])
237
+ if len(parts) >= _WEIGHT_COMPONENT_COUNT:
238
+ weights.append(parts[_WEIGHT_COMPONENT_INDEX])
239
+ else:
240
+ weights.append(1.0)
241
+ return points, weights
242
+
243
+
244
+ def _looks_like_kpoint_line(line: str) -> bool:
245
+ """Return ``True`` when the first three tokens are parseable floats.
246
+
247
+ Extended Summary
248
+ ----------------
249
+ Heuristic used to distinguish a k-point coordinate line from a
250
+ coordinate-mode descriptor line (e.g. ``"Reciprocal"``) when the
251
+ KPOINTS file uses explicit mode and the mode keyword also happens
252
+ to appear on line 3. Lines with fewer than 3 tokens or with
253
+ non-numeric leading tokens return ``False``.
254
+
255
+ Parameters
256
+ ----------
257
+ line : str
258
+ A single line from the KPOINTS file.
259
+
260
+ Returns
261
+ -------
262
+ bool
263
+ ``True`` if the first three whitespace-separated tokens can
264
+ all be converted to ``float``; ``False`` otherwise.
265
+ """
266
+ parts: list[str] = line.split()
267
+ if len(parts) < _XYZ_COMPONENTS:
268
+ return False
269
+ try:
270
+ float(parts[0])
271
+ float(parts[1])
272
+ float(parts[2])
273
+ except ValueError:
274
+ return False
275
+ return True
276
+
277
+
278
+ def _parse_grid(line: str) -> list[int]:
279
+ """Parse automatic-mode grid line into three integers.
280
+
281
+ Extended Summary
282
+ ----------------
283
+ In VASP automatic KPOINTS mode, the line immediately after the
284
+ scheme keyword contains the three Monkhorst-Pack grid subdivisions
285
+ ``N1 N2 N3``. Tokens are parsed as floats first and then rounded
286
+ to the nearest integer to tolerate files written with decimal
287
+ points (e.g. ``"4.0 4.0 4.0"``).
288
+
289
+ Parameters
290
+ ----------
291
+ line : str
292
+ The grid-dimension line from the KPOINTS file.
293
+
294
+ Returns
295
+ -------
296
+ list[int]
297
+ Three-element list ``[N1, N2, N3]``.
298
+
299
+ Raises
300
+ ------
301
+ ValueError
302
+ If the line contains fewer than 3 whitespace-separated tokens.
303
+ """
304
+ vals: list[str] = line.split()
305
+ if len(vals) < _XYZ_COMPONENTS:
306
+ msg = "Automatic KPOINTS grid line must have 3 values."
307
+ raise ValueError(msg)
308
+ return [
309
+ int(round(float(vals[0]))),
310
+ int(round(float(vals[1]))),
311
+ int(round(float(vals[2]))),
312
+ ]
313
+
314
+
315
+ def _parse_shift(line: str) -> list[float]:
316
+ """Parse automatic-mode shift line into three floats.
317
+
318
+ Extended Summary
319
+ ----------------
320
+ In VASP automatic KPOINTS mode, the line after the grid line
321
+ contains the shift vector applied to the Monkhorst-Pack mesh.
322
+ Values of ``0 0 0`` indicate a Gamma-centred grid; non-zero
323
+ values shift the mesh in fractional reciprocal-space coordinates.
324
+
325
+ Parameters
326
+ ----------
327
+ line : str
328
+ The shift line from the KPOINTS file.
329
+
330
+ Returns
331
+ -------
332
+ list[float]
333
+ Three-element list ``[s1, s2, s3]``.
334
+
335
+ Raises
336
+ ------
337
+ ValueError
338
+ If the line contains fewer than 3 whitespace-separated tokens.
339
+ """
340
+ vals: list[str] = line.split()
341
+ if len(vals) < _XYZ_COMPONENTS:
342
+ msg = "Automatic KPOINTS shift line must have 3 values."
343
+ raise ValueError(msg)
344
+ return [float(vals[0]), float(vals[1]), float(vals[2])]
345
+
346
+
347
+ def _extract_coords(line: str) -> list[float]:
348
+ """Extract first three float tokens from a KPOINTS coordinate line.
349
+
350
+ Extended Summary
351
+ ----------------
352
+ Uses a regex (``_FLOAT_TOKEN_RE``) to find all floating-point
353
+ tokens in the line, regardless of surrounding non-numeric text
354
+ (such as symmetry labels or comment markers). This makes the
355
+ extraction robust to various KPOINTS formatting styles.
356
+
357
+ Parameters
358
+ ----------
359
+ line : str
360
+ A single k-point coordinate line.
361
+
362
+ Returns
363
+ -------
364
+ list[float]
365
+ Three-element list of the first three float values found.
366
+
367
+ Raises
368
+ ------
369
+ ValueError
370
+ If fewer than 3 float tokens are found on the line.
371
+ """
372
+ tokens: list[str] = _FLOAT_TOKEN_RE.findall(line)
373
+ if len(tokens) < _XYZ_COMPONENTS:
374
+ msg = f"Could not parse k-point coordinates from line: {line!r}"
375
+ raise ValueError(msg)
376
+ return [float(tokens[0]), float(tokens[1]), float(tokens[2])]
377
+
378
+
379
+ def _extract_label(line: str) -> str:
380
+ r"""Extract symmetry label from a KPOINTS line.
381
+
382
+ Parses a single coordinate line from a VASP KPOINTS file and
383
+ attempts to recover the human-readable symmetry label (e.g.
384
+ ``"G"``, ``"X"``, ``"M"``) that is conventionally appended after
385
+ the three fractional coordinates.
386
+
387
+ Implementation Logic
388
+ --------------------
389
+ 1. **Regex match** -- search for the pattern ``! <label>`` using
390
+ ``re.search(r"!\\s*(\\S+)", line)``. The ``!`` delimiter is the
391
+ standard VASP comment marker used by AFLOW, SeeK-path, and most
392
+ KPOINTS generators. If a match is found, return the first
393
+ captured non-whitespace group.
394
+
395
+ 2. **Fallback heuristic** -- if no ``!`` marker is present, split
396
+ the line on whitespace. If there are more than 4 tokens (three
397
+ coordinates plus at least a weight and a label), return the last
398
+ token as the label.
399
+
400
+ 3. **Default** -- if neither strategy yields a label, return an
401
+ empty string.
402
+
403
+ Parameters
404
+ ----------
405
+ line : str
406
+ A single k-point line from the KPOINTS file, typically of the
407
+ form ``"0.0 0.0 0.0 ! G"`` or ``"0.0 0.0 0.0 1 G"``.
408
+
409
+ Returns
410
+ -------
411
+ label : str
412
+ Extracted symmetry label, or ``""`` if none is found.
413
+
414
+ Notes
415
+ -----
416
+ The ``! LABEL`` convention is the most reliable. The fallback
417
+ heuristic can misidentify a numeric weight as a label when the
418
+ line has exactly 5 whitespace-separated tokens, but this situation
419
+ is rare in practice.
420
+ """
421
+ _min_parts_with_label: int = 4
422
+ match: Optional[re.Match[str]] = re.search(r"!\s*(\S+)", line)
423
+ if match:
424
+ return match.group(1)
425
+ parts: list[str] = line.split()
426
+ if len(parts) > _min_parts_with_label:
427
+ return parts[-1]
428
+ return ""
429
+
430
+
431
+ __all__: list[str] = [
432
+ "read_kpoints",
433
+ ]