Expanded Wrappers and Conventions¶
The expanded-input wrappers in diffpes.simul accept plain arrays and scalars.
They build the PyTrees internally and execute the same JAX kernels. These
wrappers support quick interactive work and migration from script-based
workflows. Such workflows include the historical ARPES_simulation_*
function family. This page defines the wrapper family and its argument
conventions. The conventions cover energy-axis padding, angle units, and array
indexing across diffpes.
Function Mapping¶
Each legacy ARPES_simulation_* entry point corresponds to exactly one
expanded wrapper:
Legacy name |
diffpes function |
Broadening |
Orbital weights |
Polarization |
|---|---|---|---|---|
|
|
Voigt ( |
uniform |
— |
|
|
Gaussian ( |
heuristic |
— |
|
|
Gaussian ( |
Yeh–Lindau |
— |
|
|
Gaussian ( |
Yeh–Lindau |
polarization rules |
|
|
Voigt ( |
Yeh–Lindau |
dipole matrix elements |
|
|
Voigt ( |
Yeh–Lindau |
dipole + spin-orbit ( |
Every wrapper accepts eigenbands [K, B], which contains eigenvalues in eV.
It also accepts surface_orb [K, B, A, 9], which contains orbital
projections. Additional scalar parameters depend on the selected level. The
wrapper builds BandStructure, OrbitalProjection, and SimulationParams
PyTrees. Applicable wrappers also build a PolarizationConfig PyTree. The
wrapper then calls the corresponding simulate_novice … simulate_soc core
function. The SOC wrapper also requires surface_spin [K, B, A, 6]. This
array uses the [Sx+, Sx-, Sy+, Sy-, Sz+, Sz-] channel convention.
Dynamic Dispatch: simulate_expanded(level=...)¶
A single entry point routes by level name (case-insensitive):
import jax.numpy as jnp
from diffpes.simul import simulate_expanded
# [nkpt, nband]
eigenbands = jnp.linspace(-2.0, 0.5, 100).reshape(20, 5)
# [nkpt, nband, natom, 9]
surface_orb = jnp.ones((20, 5, 2, 9)) * 0.1
spectrum = simulate_expanded(
level="advanced",
eigenbands=eigenbands,
surface_orb=surface_orb,
ef=0.0,
sigma=0.04,
fidelity=2500,
temperature=15.0,
photon_energy=11.0,
polarization="unpolarized",
incident_theta=45.0,
incident_phi=0.0,
polarization_angle=0.0,
)
print(spectrum.intensity.shape, spectrum.energy_axis.shape) # (20, 2500) (2500,)
Rules of the dispatcher:
levelis one of"novice","basic","basicplus","advanced","expert","soc". Anything else raisesValueErrorlisting the valid levels.The dispatcher requires only
level,eigenbands, andsurface_orb. Every other parameter has a default:ef=0.0,sigma=0.04,gamma=0.1,fidelity=25000,temperature=15.0,photon_energy=11.0, andincident_theta=45.0.The dispatcher silently ignores parameters that the selected level does not use. For example,
level="basic"ignoresgamma. The novice level ignores polarization settings.level="soc"requiressurface_spin; omitting it raisesValueError.ls_scale(default0.01) sets the spin-orbit coupling strength.
Mark level, polarization, and fidelity as static under jax.jit.
These Python str and int values select code paths and array shapes. See
JAX Transformability and Gradients.
The Returned ArpesSpectrum¶
All wrappers return the standard ArpesSpectrum PyTree from diffpes.types.
The PyTree-level API and fitting layer use the same carrier:
intensity—Float[Array, "K E"], the simulated photoemission intensity per (k-point, energy) pair.energy_axis—Float[Array, " E"], the energy grid in eV, withE == fidelitypoints.
Default Energy-Axis Padding¶
The expanded wrappers derive the energy window from the data via
make_expanded_simulation_params: the axis spans
min(eigenbands) - 1 eV to max(eigenbands) + 1 eV
with fidelity evenly spaced points. The default energy_padding is 1.0 eV.
Call make_expanded_simulation_params directly to adjust this value. In the
preceding example, eigenvalues span [-2.0, 0.5] eV. Therefore, the axis spans
[-3.0, 1.5] eV. Build SimulationParams directly when comparisons require a
fixed window. Use
diffpes.types.make_simulation_params(energy_min=..., energy_max=..., ...),
then call a PyTree-level simulate_* function.
Angle and Polarization Conventions¶
Incident angles are degrees at the wrapper boundary. The wrapper interprets
incident_thetafrom the surface normal. It interpretsincident_phias the azimuthal angle. The wrapper converts both values to radians once. It then stores them onPolarizationConfig, whosethetaandphifields use radians.polarization_angleis radians. It is the rotation angle for arbitrary linear polarization ("LAP"). The wrapper does not convert it.polarizationis a case-insensitive string:"LVP"(s-pol),"LHP"(p-pol),"RCP","LCP","LAP", or"unpolarized". The field builder maps unrecognized strings to s-polarization. Use an exact supported value.Energies use eV. Lengths use Angstrom, k-vectors use 1/Angstrom, and temperatures use Kelvin.
Python Indexing Conventions¶
diffpes uses standard Python/NumPy indexing everywhere: zero-based,
end-exclusive. This applies to atom indices (atom_indices=[0, 1] means
the first two atoms), band indices, k-point indices, and orbital channels.
The 9 orbital channels of surface_orb[..., :] follow the VASP ordering
[s, py, pz, px, dxy, dyz, dz2, dxz, dx2-y2]. The following slices select
the orbital families:
Family |
Slice |
Indices selected |
|---|---|---|
non-s orbitals |
|
1–8 (all p and d) |
p orbitals |
|
1, 2, 3 (py, pz, px) |
d orbitals |
|
4–8 (dxy, dyz, dz2, dxz, dx2-y2) |
p_weight = surface_orb[..., 1:4].sum(axis=-1) # total p character
d_weight = surface_orb[..., 4:9].sum(axis=-1) # total d character
Do not use MATLAB-style one-based, end-inclusive notation in code, comments,
or documentation. When you port a MATLAB selection of “orbitals 2:9”, use
the diffpes equivalent slice(1, 9).
Wrapper Layers¶
The API provides three tiers, from most to least manual control:
PyTree level uses
simulate_novice…simulate_socand the first-principlessimulate_tb_radial. Build every input PyTree with thediffpes.typesfactories.Expanded level uses
simulate_*_expandedorsimulate_expanded. Supply plain arrays. The wrapper assembles PyTrees and derives the energy window.Workflow level uses
run_vasp_workfloworload_vasp_contextwithsimulate_context. Start from VASP files on disk. The context loader callssimulate_expandedinternally. See VASP Data Ingestion.
All three layers return the same ArpesSpectrum. Their continuous inputs
remain differentiable. The wrappers add no stop_gradient operations.
Therefore, jax.grad reaches eigenbands, surface_orb, ef, sigma, and
the other continuous inputs. PyTree-level functions provide the same gradient
paths.