diffpes.tightb¶
Native tight-binding model construction, Slater-Koster coupling, degeneracy-safe diagonalization, and ARPES-side adapters for external electronic-structure sources.
Provide native tight-binding tools and ARPES-side adapters.
Extended Summary¶
The native tight-binding layer provides model construction and Slater-Koster
coupling. It adds spin-orbit coupling, slabs, and degeneracy-safe
diagonalization as the plan series progresses. It also consumes
DiagonalizedBands from other electronic-structure sources.
This module retains:
ARPES-side adapters that stay here permanently:
vasp_to_diagonalized,eigenvector_orbital_weights,orbital_coefficients.Current fixtures, superseded by plan 04:
build_hamiltonian_k,diagonalize_single_k,diagonalize_tb.
The following list describes the submodules:
diagonalizeDiagonalize bands and adapt VASP outputs.
hamiltonianBuild tight-binding Hamiltonians in JAX.
kspaceBuild differentiable paths and fixed-shape rasters in k-space.
projectionsConvert eigenvectors to orbital weights.
Routine Listings¶
build_hamiltonian_k()Build the Bloch Hamiltonian H(k) at a single k-point.
build_arpes_kmesh()Build a fixed-kz ARPES raster in fractional coordinates.
build_bz_mesh()Build a fixed-shape reciprocal mesh and its first-zone mask.
build_kmesh_hv()Build a photon-energy raster in fractional coordinates.
build_kpath()Build a labeled path between k-space anchors.
diagonalize_single_k()Diagonalize H(k) at a single k-point.
diagonalize_tb()Diagonalize a TB model at all k-points.
eigenvector_orbital_weights()Compute orbital weights from eigenvectors.
first_bz_mask()Mark Cartesian points inside the first Brillouin zone.
kpath_arc_length()Compute cumulative Cartesian distance along a k-path.
kpoints_cart_to_frac()Convert Cartesian momenta to fractional k-points.
kpoints_frac_to_cart()Convert fractional k-points to Cartesian momenta.
orbital_coefficients()Return the raw complex orbital coefficients.
vasp_to_diagonalized()Convert VASP BandStructure + OrbitalProjection to DiagonalizedBands.
- diffpes.tightb.diagonalize_single_k(H_k: Complex[Array, 'O O']) tuple[Float[Array, 'O'], Complex[Array, 'O O']][source]¶
Diagonalize H(k) at a single k-point.
The function calls the standard LAPACK-style Hermitian eigensolver
jnp.linalg.eigh. The Hamiltonian construction guarantees Hermitian symmetry. Therefore,eighproduces real eigenvalues and an orthonormal eigenvector basis.jnp.linalg.eighreturns eigenvalues in ascending order and eigenvectors as columns of the returned matrix: that is,eigenvectors[:, i]is the eigenvector corresponding toeigenvalues[i]. This column-eigenvector convention is the LAPACK/NumPy/JAX standard but differs from some physics textbooks that store eigenvectors as rows. The caller (diagonalize_tb) transposes to a band-major layout after vmapping.This function contains one call to
eigh. Tests can isolate or replace this function with a custom differentiable eigensolver.- See:
- Parameters:
H_k (
Complex[Array, 'O O']) – Hermitian Hamiltonian matrix.- Return type:
tuple[Float[Array, 'O'],Complex[Array, 'O O']]- Returns:
eigenvalues (
Float[Array," O"]) – Eigenvalues in ascending order.eigenvectors (
Complex[Array,"O O"]) – Eigenvector columns (eigenvectors[:, i] is the i-th).
Notes
JAX provides analytical gradients through
eighvia implicit differentiation of the eigenvalue equation, sojax.grad(lambda p: eigenvalues(p).sum())works without additional configuration. Degenerate eigenvalues can cause numerical instability in the backward pass; this is a known JAX limitation.
- diffpes.tightb.diagonalize_tb(tb_model: TBModel, kpoints: Float[Array, 'K 3']) DiagonalizedBands[source]¶
Diagonalize a TB model at all k-points.
The function builds H(k) for each k-point and calls
jnp.linalg.eigh.jax.vmapvectorizes both operations. JAX differentiates them with respect totb_model.hopping_params.The internal
_build_and_diagclosure captures the model parameters. It maps one k-point to its eigenvalues and eigenvectors.jax.vmapapplies this closure along the leading k-point axis. One vectorized call computes the complete band structure without Python loops.After vmapping, the eigenvector array has shape
(K, O, O)in the column-eigenvector convention ofeigh. Thus,evecs[k, :, b]is bandbat k-pointk). A transpose(0, 2, 1)converts this to the band-major convention(K, B, O)whereevecs[k, b, :]gives the orbital coefficients of bandbat k-pointk. This layout matches theDiagonalizedBands.eigenvectorscontract used by the rest of the ARPYES pipeline (projections, matrix elements, spectral function).The function sets the Fermi energy to 0.0 because bare tight-binding models have no absolute energy reference.
- See:
- Parameters:
tb_model (
TBModel) – Tight-binding model.kpoints (
Float[Array, 'K 3']) – k-points in fractional coordinates.
- Returns:
bands – Diagonalized electronic structure.
- Return type:
Notes
Because
jax.vmaptraces the function once and broadcasts over the batch dimension, the number of k-points does not affect compilation time – only runtime. The full forward + backward pass (eigenvalues and their gradients with respect to hopping parameters) is differentiable end-to-end.
- diffpes.tightb.vasp_to_diagonalized(bands: BandStructure, orb_proj: OrbitalProjection, orbital_basis: OrbitalBasis, phase_loss: Literal['warn', 'ignore', 'error'] = 'warn') DiagonalizedBands[source]¶
Convert VASP BandStructure + OrbitalProjection to DiagonalizedBands.
VASP PROCAR gives
|c_{k,b,orb}|^2, not the complex coefficients. This adapter usessqrt(|c|^2)with positive sign as an approximation. Phase information is lost.The adapter sums the orbital projections over atoms. It then maps them to the orbital basis order.
VASP’s PROCAR file provides site- and orbital-projected squared moduli
|c_{k,b,atom,orb}|^2for each eigenstate. The VASP projection discards the complex phase of each coefficient. Therefore, PROCAR data cannot recover the true complex eigenvectors. This adapter takessqrt(|c|^2)with a positive sign. The approximation produces real, nonnegative coefficients.The approximation is exact for an observable that depends only on the coefficient modulus. It introduces errors when an observable depends on relative orbital phases. Photoemission interference terms have this phase dependence.
Orbital mapping. VASP stores projections in a fixed 9-orbital ordering for the s, p, and d channels:
[s, py, pz, px, dxy, dyz, dz2, dxz, dx2 - y2]
This order differs from some standard orders, such as the
(l, m)convention with m running from -l to +l). The function maps from(l, m)quantum numbers in theOrbitalBasisto VASP’s 9-orbital column index via a lookup table.Sum the atoms. Before the orbital mapping, the adapter sums the projections over atom axis 2, which changes
(K, B, A, 9) -> (K, B, 9). This is correct when theOrbitalBasisdescribes a single composite orbital per(l, m)channel rather than per-atom resolution.Normalize the vectors. After the square root, the adapter normalizes each k-point and band eigenvector. Therefore,
sum_orb |c_{k,b,orb}|^2 = 1. A safe division selects a zero vector and zero gradient when all projections equal zero.- See:
- Parameters:
bands (
BandStructure) – VASP eigenvalues and k-points.orb_proj (
OrbitalProjection) – VASP orbital projections of shape (K, B, A, 9).orbital_basis (
OrbitalBasis) – Quantum number metadata defining which VASP orbital indices to use.phase_loss (
Literal['warn','ignore','error'], default:"warn") – Policy for handling lost phase information: -"warn"(default): emit a runtime warning. -"ignore": proceed without a warning. -"error": raiseValueErrorand abort.
- Returns:
diag – Approximate diagonalized bands.
- Return type:
- Raises:
ValueError – If
phase_lossis invalid, is"error", or the orbital basis contains a channel outside the VASP nine-orbital set.
Notes
The VASP 9-orbital ordering used here covers s, p, and d channels only. The adapter does not support f-orbital projections and raises
ValueErrorfor them. A PROCAR file fromLORBIT=11or higher contains all nine channels.LORBIT=10can omit the decomposition by m and does not support this adapter.The adapter converts the resulting eigenvectors to
complex128for theDiagonalizedBandstype. Their imaginary parts remain zero.
- diffpes.tightb.build_hamiltonian_k(k: Float[Array, '3'], hopping_params: Float[Array, 'H'], hopping_indices: tuple, n_orbitals: int, lattice_vectors: Float[Array, '3 3']) Complex[Array, 'O O'][source]¶
Build the Bloch Hamiltonian H(k) at a single k-point.
\[H_{ij}(\mathbf{k}) = \sum_{\mathbf{R}} t_{ij,\mathbf{R}} \exp(i \mathbf{k} \cdot \mathbf{R})\]where the sum runs over lattice vectors R defined by the hopping indices. The function makes the result Hermitian:
H = (H_raw + H_raw^dag) / 2.The function computes the Bloch sum entirely in fractional coordinates.
kuses reciprocal lattice units, and each hoppingRuses direct lattice units. Therefore,k_frac . R_fracincludes the metric. The Cartesian phase equalsexp(2 pi i k_frac . R_frac). This stage does not need the lattice matrix. The model retains it for Cartesian conversions.The function iterates over each hopping in Python because the hopping count is small and static. For each hopping, it computes the Bloch phase. It adds the corresponding amplitude to
H[orb_i, orb_j].After the accumulation, the function applies
(H + H^dag) / 2. A user-supplied hopping list can contain only the upper triangle. This operation fills the lower triangle symmetrically. It also corrects floating-point rounding that can preventjnp.linalg.eighfrom working.- See:
- Parameters:
k (
Float[Array, '3']) – k-point in fractional coordinates.hopping_params (
Float[Array, 'H']) – Hopping amplitudes (differentiable).hopping_indices (
tuple) –(orb_i, orb_j, (R_x, R_y, R_z))per hopping.n_orbitals (
int) – Number of orbitals in the unit cell.lattice_vectors (
Float[Array, '3 3']) – Real-space lattice vectors (rows).
- Returns:
H_k – Hermitian Hamiltonian matrix.
- Return type:
Complex[Array, 'O O']
Notes
The hopping amplitudes
hopping_paramsare plain real floats. Complex spin-orbit hoppings requirecomplex128values and no automatic Hermitian completion.The
@jaxtypedand@beartypedecorators detect shape and dtype errors when the caller invokes the function.
- diffpes.tightb.build_arpes_kmesh(kx_axis_inv_ang: Float[Array, 'n_kx'], ky_axis_inv_ang: Float[Array, 'n_ky'], kz_inv_ang: float | Float[Array, ''], sample_azimuth: float | Float[Array, ''], geometry: CrystalGeometry) KGrid[source]¶
Build a fixed-kz ARPES raster in fractional coordinates.
The input axes describe the laboratory frame. A negative sample azimuth rotates that raster into the crystal frame before fractional conversion.
- See:
Implementation Logic¶
Build the laboratory raster:
lab_x, lab_y = jnp.meshgrid(kx_axis, ky_axis)
The result uses rows for y and columns for x.
Apply the sample rotation:
sample_x = cosine * lab_x + sine * lab_y
This expression applies an active rotation by negative azimuth.
Convert and return the grid:
return make_kgrid(kpoints_frac, mesh_shape, kz)
The fixed
kzvalue remains a traced carrier leaf.
- param kx_axis_inv_ang:
Laboratory x momenta in 1/Angstrom.
- type kx_axis_inv_ang:
Float[Array, 'n_kx']- param ky_axis_inv_ang:
Laboratory y momenta in 1/Angstrom.
- type ky_axis_inv_ang:
Float[Array, 'n_ky']- param kz_inv_ang:
Fixed out-of-plane momentum in 1/Angstrom.
- type kz_inv_ang:
Union[float,Float[Array, '']]- param sample_azimuth:
Sample rotation about the surface normal in radians.
- type sample_azimuth:
Union[float,Float[Array, '']]- param geometry:
Crystal geometry for Cartesian-to-fractional conversion.
- type geometry:
- returns:
kgrid – Fractional raster with static shape
(n_ky, n_kx).- rtype:
Notes
Gradients flow through both axes,
kz, the azimuth, and the lattice. The raster shape depends only on the static input shapes.
- diffpes.tightb.build_bz_mesh(geometry: CrystalGeometry, n_per_axis: int, shell_radius: int = 2) tuple[KGrid, Bool[Array, 'n_k']][source]¶
Build a fixed-shape reciprocal mesh and its first-zone mask.
The mesh samples the fractional cube from -1 to 1. A basis-derived bound must prove that this cube contains the complete first zone.
- See:
Implementation Logic¶
Build the fractional cube:
mesh_x, mesh_y, mesh_z = jnp.meshgrid(axis, axis, axis)
Static axis lengths give the flattened point array a fixed shape.
Compute zone membership:
mask = first_bz_mask(kpoints_cart, geometry)
The mask preserves the fixed point count during compiled execution.
Return the grid and mask:
return kgrid, mask
The grid stores the three-dimensional mesh as rows of z coordinates.
- param geometry:
Crystal geometry for reciprocal conversion.
- type geometry:
- param n_per_axis:
Number of samples on each fractional axis. This value is static. A change causes retracing.
- type n_per_axis:
- param shell_radius:
Static reciprocal-coefficient radius for the first-zone test. Increase this value for a skew, anisotropic, or unreduced basis. Default is 2.
- type shell_radius:
int, default:2- rtype:
- returns:
kgrid (
KGrid) – Fractional mesh withn_per_axis**3flattened points.mask (
Bool[Array," n_k"]) – First-zone membership for every mesh point.
- raises ValueError:
If
n_per_axisis less than two orshell_radiusis less than one.- raises EquinoxRuntimeError:
If the reciprocal basis does not prove that the fractional cube contains the first zone. The function also raises if the shell is not provably complete for the provisional first-zone points.
Notes
The static mesh shape is
(n_per_axis**2, n_per_axis). Each row follows the z axis for one pair of x and y coordinates.The coverage guard uses the inequalities
abs(k dot b_i) <= norm(b_i)**2 / 2that every first-zone point obeys. It maps their coordinate-wise bounds through the reciprocal Gram matrix. A failed conservative bound asks the caller for a reduced reciprocal basis instead of returning a partial zone.
- diffpes.tightb.build_kmesh_hv(kpar_axis_inv_ang: Float[Array, 'n_kpar'], photon_energies_ev: Float[Array, 'n_hv'], work_function_ev: float | Float[Array, ''], inner_potential_ev: float | Float[Array, ''], sample_azimuth: float | Float[Array, ''], kpar_direction: Float[Array, '2'], geometry: CrystalGeometry) KGrid[source]¶
Build a photon-energy raster in fractional coordinates.
Each row contains one photon energy and all requested parallel momenta. The free-electron final-state model supplies the row-dependent
kz.- See:
Implementation Logic¶
Compute each out-of-plane row:
kz_rows = jax.vmap(kz_for_energy)(photon_energies_ev)
One vectorized map preserves the dense photon-energy axis.
Build and rotate the Cartesian raster:
sample_x = cosine * lab_x + sine * lab_y
The traced azimuth maps the laboratory direction into the sample.
Convert and return the grid:
return make_kgrid(..., photon_energy_axis_ev=photon_energies_ev)
The carrier records one photon energy for each raster row.
- param kpar_axis_inv_ang:
Signed parallel momenta in 1/Angstrom.
- type kpar_axis_inv_ang:
Float[Array, 'n_kpar']- param photon_energies_ev:
Photon energies in eV.
- type photon_energies_ev:
Float[Array, 'n_hv']- param work_function_ev:
Work function in eV.
- type work_function_ev:
Union[float,Float[Array, '']]- param inner_potential_ev:
Inner potential in eV.
- type inner_potential_ev:
Union[float,Float[Array, '']]- param sample_azimuth:
Sample rotation about the surface normal in radians.
- type sample_azimuth:
Union[float,Float[Array, '']]- param kpar_direction:
Unit direction in the laboratory surface plane.
- type kpar_direction:
Float[Array, '2']- param geometry:
Crystal geometry for Cartesian-to-fractional conversion.
- type geometry:
- returns:
kgrid – Fractional raster with static shape
(n_hv, n_kpar).- rtype:
- raises EquinoxRuntimeError:
If the direction is not finite and unit length. The function also rejects a raster that contains an evanescent channel.
Notes
The
KGridcarrier stores no scalarkzbecause each row has a different value. The third coordinate of each point stores that value.
- diffpes.tightb.build_kpath(anchors: Float[Array, 'n_anchor 3'], geometry: CrystalGeometry, n_per_segment: int, labels: tuple[str, ...], anchor_units: str = 'fractional') KPath[source]¶
Build a labeled path between k-space anchors.
Each segment contains both endpoints. Adjacent segments therefore repeat their shared anchor, which matches the Chinook
klib.kpathconvention.- See:
Implementation Logic¶
Convert the anchors:
fractional_anchors = kpoints_cart_to_frac(anchors, geometry)
This branch applies only to anchors with absolute units.
Interpolate all segments:
segments = starts + fractions * (ends - starts)
A broadcast creates every segment with a fixed output shape.
Create the path carrier:
return make_kpath(...)
Static indices identify each anchor in the flattened point array.
- param anchors:
Path anchors in the coordinates selected by
anchor_units.- type anchors:
Float[Array, 'n_anchor 3']- param geometry:
Crystal geometry for absolute-to-fractional conversion.
- type geometry:
- param n_per_segment:
Points in each segment, including both endpoints. This value is static. A change causes retracing.
- type n_per_segment:
- param labels:
One label for each anchor. This value is static. A change causes retracing.
- type labels:
- param anchor_units:
Static coordinate selector. Use
"fractional"or"absolute". A change causes retracing. Default is"fractional".- type anchor_units:
str, default:"fractional"- returns:
kpath – Fractional path with
(n_anchor - 1) * n_per_segmentpoints.- rtype:
- raises ValueError:
If the path has fewer than two anchors or invalid static settings.
- raises EquinoxRuntimeError:
If an anchor is non-finite.
Notes
"absolute"means Cartesian momentum in 1/Angstrom. The interpolation remains differentiable with respect to anchors and the lattice.
- diffpes.tightb.first_bz_mask(kpoints_cart: Float[Array, 'n_k 3'], geometry: CrystalGeometry, shell_radius: int = 2) Bool[Array, 'n_k'][source]¶
Mark Cartesian points inside the first Brillouin zone.
The Wigner-Seitz test compares the origin distance with reciprocal-lattice points in a static integer shell. A singular-value bound proves that no point outside the shell can change a retained membership result.
- See:
- Parameters:
kpoints_cart (
Float[Array, 'n_k 3']) – Cartesian momenta in 1/Angstrom.geometry (
CrystalGeometry) – Crystal geometry with reciprocal vectors in 1/Angstrom.shell_radius (
int, default:2) – Static maximum absolute reciprocal coefficient. Increase this value for a skew, anisotropic, or unreduced basis. Default is 2.
- Returns:
mask – True for points inside or on the first-zone boundary.
- Return type:
Bool[Array, 'n_k']- Raises:
ValueError – If
shell_radiusis less than one.EquinoxRuntimeError – If the static shell is not provably complete for the supplied points and reciprocal basis.
Notes
The comparisons use squared distances and include ties. The Boolean mask has no boundary derivative. Consumers must not differentiate through its discrete membership changes.
For any unseen reciprocal vector
G = n @ B, its norm is at leastsigma_min(B) * norm(n). A vector can beat the origin only whennorm(G) <= 2 * norm(k). The function checks this sufficient bound for every point that survives the requested shell. It raises instead of returning an uncertified false positive when the bound is inconclusive.
- diffpes.tightb.kpath_arc_length(kpath: KPath, geometry: CrystalGeometry) Float[Array, 'n_k'][source]¶
Compute cumulative Cartesian distance along a k-path.
The plotting coordinate measures each segment after reciprocal-lattice conversion. Repeated junction points add zero distance.
- See:
- Parameters:
kpath (
KPath) – Fractional path and its static plotting metadata.geometry (
CrystalGeometry) – Crystal geometry with reciprocal vectors in 1/Angstrom.
- Returns:
arc_length – Cumulative Cartesian path distance in 1/Angstrom.
- Return type:
Float[Array, 'n_k']
Notes
diffpes.maths.safe_norm()gives a zero gradient for a repeated junction. All nonzero segment lengths retain their usual gradients.
- diffpes.tightb.kpoints_cart_to_frac(kpoints_cart: Float[Array, 'n_k 3'], geometry: CrystalGeometry) Float[Array, 'n_k 3'][source]¶
Convert Cartesian momenta to fractional k-points.
The function uses the closed reciprocal identity instead of a numerical linear solve.
- See:
- Parameters:
kpoints_cart (
Float[Array, 'n_k 3']) – Cartesian momenta in 1/Angstrom.geometry (
CrystalGeometry) – Crystal geometry with real-space vectors in Angstrom.
- Returns:
kpoints_frac – Fractional k-points.
- Return type:
Float[Array, 'n_k 3']
Notes
Reciprocal and real-space lattice rows satisfy \(B A^T=2\pi I\). Thus, the exact expression is
kpoints_cart @ geometry.lattice.T / (2 * pi).
- diffpes.tightb.kpoints_frac_to_cart(kpoints_frac: Float[Array, 'n_k 3'], geometry: CrystalGeometry) Float[Array, 'n_k 3'][source]¶
Convert fractional k-points to Cartesian momenta.
The row-vector convention applies the reciprocal lattice once. The reciprocal lattice includes the factor of \(2\pi\).
- See:
- Parameters:
kpoints_frac (
Float[Array, 'n_k 3']) – Fractional k-points.geometry (
CrystalGeometry) – Crystal geometry with reciprocal vectors in 1/Angstrom.
- Returns:
kpoints_cart – Cartesian momenta in 1/Angstrom.
- Return type:
Float[Array, 'n_k 3']
Notes
The function computes
kpoints_frac @ geometry.reciprocal. Gradients flow through the coordinates and all applicable lattice entries.
- diffpes.tightb.eigenvector_orbital_weights(eigenvectors: Complex[Array, 'K B O']) Float[Array, 'K B O'][source]¶
Compute orbital weights from eigenvectors.
For each eigenstate
|psi_{k,b}>expanded in the orbital basis\[|\psi_{k,b}\rangle = \sum_o c_{k,b,o} |o\rangle,\]the orbital weight of orbital
oin bandbat k-pointkis the squared modulus of the expansion coefficient:\[w_{k,b,o} = |c_{k,b,o}|^2.\]This is the probability of finding the electron in orbital
ogiven that it occupies eigenstate(k, b). By construction, normalized eigenvectors give weights that sum to 1 over orbitals for each(k, b)pair.Fat-band plots and orbital-resolved DOS use orbital weights. Photoemission matrix element computations also start from these weights. These computations also need the complex coefficients; see
orbital_coefficients.- See:
- Parameters:
eigenvectors (
Complex[Array, 'K B O']) – Complex orbital coefficients c_{k,b,orb}.- Returns:
weights –
|c_{k,b,orb}|^2per orbital.- Return type:
Float[Array, 'K B O']
Notes
The implementation uses
jnp.abs(eigenvectors) ** 2rather than(eigenvectors * eigenvectors.conj()).realfor clarity. Both expressions are mathematically identical for complex arrays and produce the same JAX trace; the former is marginally more readable.
- diffpes.tightb.orbital_coefficients(eigenvectors: Complex[Array, 'K B O']) Complex[Array, 'K B O'][source]¶
Return the raw complex orbital coefficients.
A full matrix element computation needs the complex coefficients c_{k,b,orb}, not only
|c|^2.This is an identity function: it returns its input unchanged. Its purpose gives a clear name to each call site. A pipeline can need both
eigenvector_orbital_weightsand the raw coefficients. Callers can write:weights = eigenvector_orbital_weights(evecs) coeffs = orbital_coefficients(evecs)
Thus, each downstream path clearly identifies whether it uses only magnitudes or the full phase information.
In the Chinook-style matrix element computation, the function multiplies each complex coefficient by the applicable one-electron dipole matrix element. It then adds the products coherently. Interference between orbital channels depends on the relative phases of these coefficients. Therefore, this computation needs the complex values, not only
|c|^2.When
vasp_to_diagonalizedsupplies the eigenvectors, the adapter loses the phases. The adapter then returns real, nonnegative coefficients. In that regime, the coherent interference terms are approximate.- See:
- Parameters:
eigenvectors (
Complex[Array, 'K B O']) – Complex orbital coefficients.- Returns:
coefficients – Same as input (identity, for API clarity).
- Return type:
Complex[Array, 'K B O']
Notes
Because this is an identity,
jax.gradthroughorbital_coefficientsadds zero overhead – the function compiles away entirely.
K-space builders¶
Build differentiable paths and fixed-shape rasters in k-space.
Extended Summary¶
This module converts fractional and Cartesian k-points through one reciprocal lattice contract. It also builds paths, reciprocal-space meshes, and ARPES rasters without data-dependent output shapes.
Routine Listings¶
kpoints_frac_to_cart()Convert fractional k-points to Cartesian momenta.
kpoints_cart_to_frac()Convert Cartesian momenta to fractional k-points.
build_kpath()Build a labeled path between k-space anchors.
kpath_arc_length()Compute cumulative Cartesian distance along a k-path.
first_bz_mask()Mark Cartesian points inside the first Brillouin zone.
build_bz_mesh()Build a fixed-shape reciprocal mesh and its first-zone mask.
build_arpes_kmesh()Build a fixed-kz ARPES raster in fractional coordinates.
build_kmesh_hv()Build a photon-energy raster in fractional coordinates.
Notes
Every conversion uses reciprocal vectors as matrix rows. The first-zone operation returns a mask because a gather creates a data-dependent shape.
- diffpes.tightb.kspace.kpoints_frac_to_cart(kpoints_frac: Float[Array, 'n_k 3'], geometry: CrystalGeometry) Float[Array, 'n_k 3'][source]¶
Convert fractional k-points to Cartesian momenta.
The row-vector convention applies the reciprocal lattice once. The reciprocal lattice includes the factor of \(2\pi\).
- See:
- Parameters:
kpoints_frac (
Float[Array, 'n_k 3']) – Fractional k-points.geometry (
CrystalGeometry) – Crystal geometry with reciprocal vectors in 1/Angstrom.
- Returns:
kpoints_cart – Cartesian momenta in 1/Angstrom.
- Return type:
Float[Array, 'n_k 3']
Notes
The function computes
kpoints_frac @ geometry.reciprocal. Gradients flow through the coordinates and all applicable lattice entries.
- diffpes.tightb.kspace.kpoints_cart_to_frac(kpoints_cart: Float[Array, 'n_k 3'], geometry: CrystalGeometry) Float[Array, 'n_k 3'][source]¶
Convert Cartesian momenta to fractional k-points.
The function uses the closed reciprocal identity instead of a numerical linear solve.
- See:
- Parameters:
kpoints_cart (
Float[Array, 'n_k 3']) – Cartesian momenta in 1/Angstrom.geometry (
CrystalGeometry) – Crystal geometry with real-space vectors in Angstrom.
- Returns:
kpoints_frac – Fractional k-points.
- Return type:
Float[Array, 'n_k 3']
Notes
Reciprocal and real-space lattice rows satisfy \(B A^T=2\pi I\). Thus, the exact expression is
kpoints_cart @ geometry.lattice.T / (2 * pi).
- diffpes.tightb.kspace.build_kpath(anchors: Float[Array, 'n_anchor 3'], geometry: CrystalGeometry, n_per_segment: int, labels: tuple[str, ...], anchor_units: str = 'fractional') KPath[source]¶
Build a labeled path between k-space anchors.
Each segment contains both endpoints. Adjacent segments therefore repeat their shared anchor, which matches the Chinook
klib.kpathconvention.- See:
Implementation Logic¶
Convert the anchors:
fractional_anchors = kpoints_cart_to_frac(anchors, geometry)
This branch applies only to anchors with absolute units.
Interpolate all segments:
segments = starts + fractions * (ends - starts)
A broadcast creates every segment with a fixed output shape.
Create the path carrier:
return make_kpath(...)
Static indices identify each anchor in the flattened point array.
- param anchors:
Path anchors in the coordinates selected by
anchor_units.- type anchors:
Float[Array, 'n_anchor 3']- param geometry:
Crystal geometry for absolute-to-fractional conversion.
- type geometry:
- param n_per_segment:
Points in each segment, including both endpoints. This value is static. A change causes retracing.
- type n_per_segment:
- param labels:
One label for each anchor. This value is static. A change causes retracing.
- type labels:
- param anchor_units:
Static coordinate selector. Use
"fractional"or"absolute". A change causes retracing. Default is"fractional".- type anchor_units:
str, default:"fractional"- returns:
kpath – Fractional path with
(n_anchor - 1) * n_per_segmentpoints.- rtype:
- raises ValueError:
If the path has fewer than two anchors or invalid static settings.
- raises EquinoxRuntimeError:
If an anchor is non-finite.
Notes
"absolute"means Cartesian momentum in 1/Angstrom. The interpolation remains differentiable with respect to anchors and the lattice.
- diffpes.tightb.kspace.kpath_arc_length(kpath: KPath, geometry: CrystalGeometry) Float[Array, 'n_k'][source]¶
Compute cumulative Cartesian distance along a k-path.
The plotting coordinate measures each segment after reciprocal-lattice conversion. Repeated junction points add zero distance.
- See:
- Parameters:
kpath (
KPath) – Fractional path and its static plotting metadata.geometry (
CrystalGeometry) – Crystal geometry with reciprocal vectors in 1/Angstrom.
- Returns:
arc_length – Cumulative Cartesian path distance in 1/Angstrom.
- Return type:
Float[Array, 'n_k']
Notes
diffpes.maths.safe_norm()gives a zero gradient for a repeated junction. All nonzero segment lengths retain their usual gradients.
- diffpes.tightb.kspace.first_bz_mask(kpoints_cart: Float[Array, 'n_k 3'], geometry: CrystalGeometry, shell_radius: int = 2) Bool[Array, 'n_k'][source]¶
Mark Cartesian points inside the first Brillouin zone.
The Wigner-Seitz test compares the origin distance with reciprocal-lattice points in a static integer shell. A singular-value bound proves that no point outside the shell can change a retained membership result.
- See:
- Parameters:
kpoints_cart (
Float[Array, 'n_k 3']) – Cartesian momenta in 1/Angstrom.geometry (
CrystalGeometry) – Crystal geometry with reciprocal vectors in 1/Angstrom.shell_radius (
int, default:2) – Static maximum absolute reciprocal coefficient. Increase this value for a skew, anisotropic, or unreduced basis. Default is 2.
- Returns:
mask – True for points inside or on the first-zone boundary.
- Return type:
Bool[Array, 'n_k']- Raises:
ValueError – If
shell_radiusis less than one.EquinoxRuntimeError – If the static shell is not provably complete for the supplied points and reciprocal basis.
Notes
The comparisons use squared distances and include ties. The Boolean mask has no boundary derivative. Consumers must not differentiate through its discrete membership changes.
For any unseen reciprocal vector
G = n @ B, its norm is at leastsigma_min(B) * norm(n). A vector can beat the origin only whennorm(G) <= 2 * norm(k). The function checks this sufficient bound for every point that survives the requested shell. It raises instead of returning an uncertified false positive when the bound is inconclusive.
- diffpes.tightb.kspace.build_bz_mesh(geometry: CrystalGeometry, n_per_axis: int, shell_radius: int = 2) tuple[KGrid, Bool[Array, 'n_k']][source]¶
Build a fixed-shape reciprocal mesh and its first-zone mask.
The mesh samples the fractional cube from -1 to 1. A basis-derived bound must prove that this cube contains the complete first zone.
- See:
Implementation Logic¶
Build the fractional cube:
mesh_x, mesh_y, mesh_z = jnp.meshgrid(axis, axis, axis)
Static axis lengths give the flattened point array a fixed shape.
Compute zone membership:
mask = first_bz_mask(kpoints_cart, geometry)
The mask preserves the fixed point count during compiled execution.
Return the grid and mask:
return kgrid, mask
The grid stores the three-dimensional mesh as rows of z coordinates.
- param geometry:
Crystal geometry for reciprocal conversion.
- type geometry:
- param n_per_axis:
Number of samples on each fractional axis. This value is static. A change causes retracing.
- type n_per_axis:
- param shell_radius:
Static reciprocal-coefficient radius for the first-zone test. Increase this value for a skew, anisotropic, or unreduced basis. Default is 2.
- type shell_radius:
int, default:2- rtype:
- returns:
kgrid (
KGrid) – Fractional mesh withn_per_axis**3flattened points.mask (
Bool[Array," n_k"]) – First-zone membership for every mesh point.
- raises ValueError:
If
n_per_axisis less than two orshell_radiusis less than one.- raises EquinoxRuntimeError:
If the reciprocal basis does not prove that the fractional cube contains the first zone. The function also raises if the shell is not provably complete for the provisional first-zone points.
Notes
The static mesh shape is
(n_per_axis**2, n_per_axis). Each row follows the z axis for one pair of x and y coordinates.The coverage guard uses the inequalities
abs(k dot b_i) <= norm(b_i)**2 / 2that every first-zone point obeys. It maps their coordinate-wise bounds through the reciprocal Gram matrix. A failed conservative bound asks the caller for a reduced reciprocal basis instead of returning a partial zone.
- diffpes.tightb.kspace.build_arpes_kmesh(kx_axis_inv_ang: Float[Array, 'n_kx'], ky_axis_inv_ang: Float[Array, 'n_ky'], kz_inv_ang: float | Float[Array, ''], sample_azimuth: float | Float[Array, ''], geometry: CrystalGeometry) KGrid[source]¶
Build a fixed-kz ARPES raster in fractional coordinates.
The input axes describe the laboratory frame. A negative sample azimuth rotates that raster into the crystal frame before fractional conversion.
- See:
Implementation Logic¶
Build the laboratory raster:
lab_x, lab_y = jnp.meshgrid(kx_axis, ky_axis)
The result uses rows for y and columns for x.
Apply the sample rotation:
sample_x = cosine * lab_x + sine * lab_y
This expression applies an active rotation by negative azimuth.
Convert and return the grid:
return make_kgrid(kpoints_frac, mesh_shape, kz)
The fixed
kzvalue remains a traced carrier leaf.
- param kx_axis_inv_ang:
Laboratory x momenta in 1/Angstrom.
- type kx_axis_inv_ang:
Float[Array, 'n_kx']- param ky_axis_inv_ang:
Laboratory y momenta in 1/Angstrom.
- type ky_axis_inv_ang:
Float[Array, 'n_ky']- param kz_inv_ang:
Fixed out-of-plane momentum in 1/Angstrom.
- type kz_inv_ang:
Union[float,Float[Array, '']]- param sample_azimuth:
Sample rotation about the surface normal in radians.
- type sample_azimuth:
Union[float,Float[Array, '']]- param geometry:
Crystal geometry for Cartesian-to-fractional conversion.
- type geometry:
- returns:
kgrid – Fractional raster with static shape
(n_ky, n_kx).- rtype:
Notes
Gradients flow through both axes,
kz, the azimuth, and the lattice. The raster shape depends only on the static input shapes.
- diffpes.tightb.kspace.build_kmesh_hv(kpar_axis_inv_ang: Float[Array, 'n_kpar'], photon_energies_ev: Float[Array, 'n_hv'], work_function_ev: float | Float[Array, ''], inner_potential_ev: float | Float[Array, ''], sample_azimuth: float | Float[Array, ''], kpar_direction: Float[Array, '2'], geometry: CrystalGeometry) KGrid[source]¶
Build a photon-energy raster in fractional coordinates.
Each row contains one photon energy and all requested parallel momenta. The free-electron final-state model supplies the row-dependent
kz.- See:
Implementation Logic¶
Compute each out-of-plane row:
kz_rows = jax.vmap(kz_for_energy)(photon_energies_ev)
One vectorized map preserves the dense photon-energy axis.
Build and rotate the Cartesian raster:
sample_x = cosine * lab_x + sine * lab_y
The traced azimuth maps the laboratory direction into the sample.
Convert and return the grid:
return make_kgrid(..., photon_energy_axis_ev=photon_energies_ev)
The carrier records one photon energy for each raster row.
- param kpar_axis_inv_ang:
Signed parallel momenta in 1/Angstrom.
- type kpar_axis_inv_ang:
Float[Array, 'n_kpar']- param photon_energies_ev:
Photon energies in eV.
- type photon_energies_ev:
Float[Array, 'n_hv']- param work_function_ev:
Work function in eV.
- type work_function_ev:
Union[float,Float[Array, '']]- param inner_potential_ev:
Inner potential in eV.
- type inner_potential_ev:
Union[float,Float[Array, '']]- param sample_azimuth:
Sample rotation about the surface normal in radians.
- type sample_azimuth:
Union[float,Float[Array, '']]- param kpar_direction:
Unit direction in the laboratory surface plane.
- type kpar_direction:
Float[Array, '2']- param geometry:
Crystal geometry for Cartesian-to-fractional conversion.
- type geometry:
- returns:
kgrid – Fractional raster with static shape
(n_hv, n_kpar).- rtype:
- raises EquinoxRuntimeError:
If the direction is not finite and unit length. The function also rejects a raster that contains an evanescent channel.
Notes
The
KGridcarrier stores no scalarkzbecause each row has a different value. The third coordinate of each point stores that value.