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,295 @@
1
+ """Parser-adjacent workflow helpers for assembling simulation-ready arrays.
2
+
3
+ Provides utilities for atom-subset aggregation, orbital channel
4
+ reductions, and cross-file consistency checks between EIGENVAL,
5
+ PROCAR, and KPOINTS parsed data.
6
+
7
+ Routine Listings
8
+ ----------------
9
+ :func:`select_atoms`
10
+ Extract orbital projections for a subset of atoms.
11
+ :func:`aggregate_atoms`
12
+ Sum orbital projections over a set of atoms.
13
+ :func:`reduce_orbitals`
14
+ Reduce 9 orbital channels to s/p/d totals.
15
+ :func:`check_consistency`
16
+ Check dimension agreement across parsed VASP files.
17
+ """
18
+
19
+ import jax.numpy as jnp
20
+ from beartype.typing import Optional, Union
21
+ from jaxtyping import Array, Float, Int
22
+
23
+ from diffpes.types import (
24
+ BandStructure,
25
+ KPathInfo,
26
+ OrbitalProjection,
27
+ SpinOrbitalProjection,
28
+ )
29
+
30
+ _S_IDX: int = 0
31
+ _P_SLICE: slice = slice(1, 4)
32
+ _D_SLICE: slice = slice(4, 9)
33
+
34
+
35
+ def select_atoms(
36
+ orb: Union[OrbitalProjection, SpinOrbitalProjection],
37
+ atom_indices: list[int],
38
+ ) -> Union[OrbitalProjection, SpinOrbitalProjection]:
39
+ """Extract orbital projections for a subset of atoms.
40
+
41
+ Extended Summary
42
+ ----------------
43
+ Creates a new projection object containing only the atoms at the
44
+ requested indices. This is the primary mechanism for isolating the
45
+ contribution of specific atomic sites (e.g. surface atoms, a
46
+ particular element) to the simulated ARPES spectrum.
47
+
48
+ Implementation Logic
49
+ --------------------
50
+ 1. Convert ``atom_indices`` to a JAX ``int32`` index array.
51
+ 2. Fancy-index the ``projections`` array along axis 2 (atom axis)
52
+ to extract the requested atoms.
53
+ 3. If ``spin`` data is present (non-``None``), apply the same
54
+ fancy-indexing to the spin array.
55
+ 4. If ``oam`` (orbital angular momentum) data is present, apply
56
+ the same fancy-indexing.
57
+ 5. Construct and return a new object of the same type as the input
58
+ (``SpinOrbitalProjection`` or ``OrbitalProjection``) so that
59
+ downstream code can treat the result identically to the
60
+ original.
61
+
62
+ Parameters
63
+ ----------
64
+ orb : OrbitalProjection or SpinOrbitalProjection
65
+ Full orbital projections with shape ``(K, B, A, 9)``.
66
+ atom_indices : list[int]
67
+ 0-based indices of atoms to select.
68
+
69
+ Returns
70
+ -------
71
+ OrbitalProjection or SpinOrbitalProjection
72
+ Projections restricted to the specified atoms.
73
+ Shape ``(K, B, len(atom_indices), 9)``.
74
+ Preserves the input type.
75
+
76
+ Notes
77
+ -----
78
+ The returned object shares no memory with the original because
79
+ JAX fancy-indexing always produces a copy. The function is
80
+ pure and can be safely used inside ``jax.jit``-compiled code.
81
+ """
82
+ idx: Int[Array, " N"] = jnp.asarray(atom_indices, dtype=jnp.int32)
83
+ proj_sub: Float[Array, "K B N 9"] = orb.projections[:, :, idx, :]
84
+ spin_sub: Optional[Float[Array, "K B N 9"]] = None
85
+ if orb.spin is not None:
86
+ spin_sub = orb.spin[:, :, idx, :]
87
+ oam_sub: Optional[Float[Array, "K B N 9"]] = None
88
+ if orb.oam is not None:
89
+ oam_sub = orb.oam[:, :, idx, :]
90
+ if isinstance(orb, SpinOrbitalProjection):
91
+ result_soc: SpinOrbitalProjection = SpinOrbitalProjection(
92
+ projections=proj_sub,
93
+ spin=spin_sub, # type: ignore[arg-type]
94
+ oam=oam_sub,
95
+ )
96
+ return result_soc
97
+ result: OrbitalProjection = OrbitalProjection(
98
+ projections=proj_sub,
99
+ spin=spin_sub,
100
+ oam=oam_sub,
101
+ )
102
+ return result
103
+
104
+
105
+ def aggregate_atoms(
106
+ orb: OrbitalProjection,
107
+ atom_indices: Optional[list[int]] = None,
108
+ ) -> Float[Array, "K B 9"]:
109
+ """Sum orbital projections over a set of atoms.
110
+
111
+ Extended Summary
112
+ ----------------
113
+ Produces a ``(K, B, 9)`` array where the atom axis has been
114
+ summed out, giving the total orbital weight at each (k-point,
115
+ band) pair. This is the standard reduction used before computing
116
+ cross-section-weighted ARPES intensities, because the simulation
117
+ only needs the aggregate orbital character rather than per-atom
118
+ contributions.
119
+
120
+ Implementation Logic
121
+ --------------------
122
+ 1. If ``atom_indices`` is not ``None``, fancy-index the
123
+ ``projections`` array along axis 2 to restrict to the
124
+ requested atoms before summing.
125
+ 2. If ``atom_indices`` is ``None``, use the full projections
126
+ array (all atoms).
127
+ 3. Sum along axis 2 (the atom axis) using ``jnp.sum`` and
128
+ return the resulting ``(K, B, 9)`` array.
129
+
130
+ Parameters
131
+ ----------
132
+ orb : OrbitalProjection
133
+ Full orbital projections with shape ``(K, B, A, 9)``.
134
+ atom_indices : list[int] or None, optional
135
+ 0-based indices of atoms to sum over. If None, sums over
136
+ all atoms.
137
+
138
+ Returns
139
+ -------
140
+ Float[Array, "K B 9"]
141
+ Atom-summed orbital projections.
142
+
143
+ Notes
144
+ -----
145
+ This function operates only on the ``projections`` field and
146
+ ignores ``spin`` and ``oam`` fields. For spin-resolved
147
+ aggregation, use :func:`select_atoms` first and then perform
148
+ the reduction manually.
149
+ """
150
+ if atom_indices is not None:
151
+ idx: Int[Array, " N"] = jnp.asarray(atom_indices, dtype=jnp.int32)
152
+ proj: Float[Array, "K B N 9"] = orb.projections[:, :, idx, :]
153
+ else:
154
+ proj = orb.projections
155
+ result: Float[Array, "K B 9"] = jnp.sum(proj, axis=2)
156
+ return result
157
+
158
+
159
+ def reduce_orbitals(
160
+ projections: Float[Array, "K B A 9"],
161
+ ) -> Float[Array, "K B A 3"]:
162
+ """Reduce 9 orbital channels to s/p/d totals.
163
+
164
+ Extended Summary
165
+ ----------------
166
+ Collapses the 9-channel VASP orbital decomposition
167
+ (``s, py, pz, px, dxy, dyz, dz2, dxz, dx2-y2``) into three
168
+ angular-momentum shell totals. This is useful for coarse-grained
169
+ orbital-character analysis (e.g. fat-band coloring by s/p/d
170
+ weight).
171
+
172
+ Implementation Logic
173
+ --------------------
174
+ 1. Extract the ``s`` channel (index 0) as-is -- shape ``(K, B, A)``.
175
+ 2. Sum the three ``p`` channels (indices 1:4) along the last axis.
176
+ 3. Sum the five ``d`` channels (indices 4:9) along the last axis.
177
+ 4. Stack ``[s_total, p_total, d_total]`` along a new trailing axis
178
+ to produce shape ``(K, B, A, 3)``.
179
+
180
+ Parameters
181
+ ----------
182
+ projections : Float[Array, "K B A 9"]
183
+ Full 9-channel orbital projections.
184
+
185
+ Returns
186
+ -------
187
+ Float[Array, "K B A 3"]
188
+ Reduced projections: ``[s_total, p_total, d_total]``.
189
+
190
+ Notes
191
+ -----
192
+ The VASP orbital ordering assumed here is:
193
+ ``[s, py, pz, px, dxy, dyz, dz2, dxz, dx2-y2]`` (indices 0-8).
194
+ This matches the standard PROCAR output when ``LORBIT=11`` or
195
+ ``LORBIT=12``.
196
+ """
197
+ s_total: Float[Array, "K B A"] = projections[..., _S_IDX]
198
+ p_total: Float[Array, "K B A"] = jnp.sum(
199
+ projections[..., _P_SLICE], axis=-1
200
+ )
201
+ d_total: Float[Array, "K B A"] = jnp.sum(
202
+ projections[..., _D_SLICE], axis=-1
203
+ )
204
+ reduced: Float[Array, "K B A 3"] = jnp.stack(
205
+ [s_total, p_total, d_total], axis=-1
206
+ )
207
+ return reduced
208
+
209
+
210
+ def check_consistency(
211
+ bands: BandStructure,
212
+ orb: OrbitalProjection,
213
+ kpath: Optional[KPathInfo] = None,
214
+ ) -> None:
215
+ """Check dimension agreement across parsed VASP files.
216
+
217
+ Extended Summary
218
+ ----------------
219
+ Validates that the k-point and band dimensions are consistent
220
+ between the EIGENVAL-derived band structure, the PROCAR-derived
221
+ orbital projections, and (optionally) the KPOINTS-derived path
222
+ metadata. This is a defensive check intended to catch mismatches
223
+ caused by mixing output files from different VASP runs.
224
+
225
+ Implementation Logic
226
+ --------------------
227
+ 1. Extract ``nkpoints`` and ``nbands`` from ``bands.eigenvalues``
228
+ (shape ``(K, B)``) and ``orb.projections`` (shape
229
+ ``(K, B, A, 9)``).
230
+ 2. Compare the k-point counts between EIGENVAL and PROCAR. Raise
231
+ ``ValueError`` with a descriptive message if they disagree.
232
+ 3. Compare the band counts between EIGENVAL and PROCAR. Raise
233
+ ``ValueError`` if they disagree.
234
+ 4. If ``kpath`` is provided and its mode is ``"Line-mode"``,
235
+ compare the total k-point count from KPOINTS
236
+ (``kpath.num_kpoints``) against the EIGENVAL k-point count.
237
+ Only check when ``num_kpoints > 0`` (i.e. the KPOINTS file
238
+ provides a concrete count).
239
+
240
+ Parameters
241
+ ----------
242
+ bands : BandStructure
243
+ Parsed EIGENVAL data.
244
+ orb : OrbitalProjection
245
+ Parsed PROCAR data.
246
+ kpath : KPathInfo or None, optional
247
+ Parsed KPOINTS data.
248
+
249
+ Raises
250
+ ------
251
+ ValueError
252
+ If k-point or band counts disagree between files.
253
+
254
+ Notes
255
+ -----
256
+ This function does not verify atom counts (PROCAR vs POSCAR)
257
+ because ``BandStructure`` does not carry atom information.
258
+ For atom-count checks, compare ``orb.projections.shape[2]``
259
+ against ``geometry.atom_counts.sum()`` manually.
260
+ """
261
+ nk_bands: int = int(bands.eigenvalues.shape[0])
262
+ nb_bands: int = int(bands.eigenvalues.shape[1])
263
+ nk_procar: int = int(orb.projections.shape[0])
264
+ nb_procar: int = int(orb.projections.shape[1])
265
+
266
+ if nk_bands != nk_procar:
267
+ msg = (
268
+ f"K-point count mismatch: EIGENVAL has {nk_bands}, "
269
+ f"PROCAR has {nk_procar}."
270
+ )
271
+ raise ValueError(msg)
272
+
273
+ if nb_bands != nb_procar:
274
+ msg = (
275
+ f"Band count mismatch: EIGENVAL has {nb_bands}, "
276
+ f"PROCAR has {nb_procar}."
277
+ )
278
+ raise ValueError(msg)
279
+
280
+ if kpath is not None and kpath.mode == "Line-mode":
281
+ nk_kpath: int = int(kpath.num_kpoints)
282
+ if nk_kpath > 0 and nk_bands != nk_kpath:
283
+ msg = (
284
+ f"K-point count mismatch: EIGENVAL has {nk_bands}, "
285
+ f"KPOINTS has {nk_kpath}."
286
+ )
287
+ raise ValueError(msg)
288
+
289
+
290
+ __all__: list[str] = [
291
+ "aggregate_atoms",
292
+ "check_consistency",
293
+ "reduce_orbitals",
294
+ "select_atoms",
295
+ ]