Source code for diffpes.simul.self_energy
r"""Evaluate energy-dependent self-energy for ARPES simulations.
Extended Summary
----------------
Evaluates the imaginary part of the electronic self-energy
(lifetime broadening) as a function of energy. The self-energy
determines the Lorentzian component of the spectral linewidth
and is critical for modelling correlated materials where
quasiparticle lifetimes vary strongly with energy.
The function supports three parametric models:
- **constant**: Uniform broadening at all energies.
- **polynomial**: Polynomial expansion in energy, for example Fermi-liquid
quadratic dependence).
- **tabulated**: Piecewise-linear interpolation from user-supplied
(energy, gamma) pairs, for example from GW or DMFT computations.
All modes are JAX-differentiable with respect to the model
coefficients, enabling gradient-based fitting of self-energy
parameters to experimental linewidths.
Routine Listings
----------------
:func:`evaluate_self_energy`
Evaluate the imaginary self-energy :math:`\Gamma(E)`.
Notes
-----
The mode string controls Python dispatch outside JAX tracing. Therefore, JAX
compiles one code path for each invocation.
"""
import jax.numpy as jnp
from beartype import beartype
from jaxtyping import Array, Float, jaxtyped
from diffpes.types import SelfEnergyConfig
[docs]
@jaxtyped(typechecker=beartype)
def evaluate_self_energy(
energy: Float[Array, " ..."],
config: SelfEnergyConfig,
) -> Float[Array, " ..."]:
r"""Evaluate the imaginary self-energy :math:`\Gamma(E)`.
The function computes an energy-dependent Lorentzian broadening width from
the specified self-energy model. The result replaces ``params.gamma`` in
the Voigt profile when the lifetime changes with energy. This behavior can
occur near the Fermi level or in correlated materials.
The imaginary part of the electron self-energy determines the
quasiparticle lifetime and thus the Lorentzian component of the
spectral linewidth. The function supports three parametric models:
- **constant**: A single scalar broadening applied uniformly at
all energies. Equivalent to using ``params.gamma`` directly.
- **polynomial**: :math:`\Gamma(E) = \sum_n c_n E^n`. Store
``coefficients`` in descending degree order for ``jnp.polyval``. This
model can represent the quadratic
Fermi-liquid self-energy :math:`\Gamma \propto (E - E_F)^2`.
- **tabulated**: Piecewise-linear interpolation of user-supplied
:math:`(\varepsilon_i, \Gamma_i)` pairs via ``jnp.interp``.
Suitable for self-energies obtained from many-body calculations
such as GW or DMFT.
:see: :class:`~.test_self_energy.TestEvaluateSelfEnergy`
Implementation Logic
--------------------
1. **Read the static mode**::
mode = config.mode
The Python string selects one computation while JAX traces its arrays.
2. **Compute the selected self-energy**::
result = jnp.broadcast_to(config.coefficients[0], energy.shape)
result = jnp.polyval(config.coefficients, energy)
result = jnp.interp(
energy, config.energy_nodes, config.coefficients
)
Each branch returns a width array with the same shape as ``energy``.
3. **Reject an unknown mode**::
raise ValueError(msg)
This static error prevents an unsupported model from entering a trace.
Parameters
----------
energy : Float[Array, " ..."]
Energy values in eV at which to evaluate the self-energy.
Any shape. The output has the same shape.
config : SelfEnergyConfig
Self-energy model specification containing:
- ``mode`` : str -- ``"constant"``, ``"polynomial"``, or
``"tabulated"``.
- ``coefficients`` : array -- Model parameters (scalar for
constant, polynomial coefficients for polynomial, gamma
values for tabulated).
- ``energy_nodes`` : array (only for ``"tabulated"``) --
Energy grid for the tabulated gamma values.
Returns
-------
result : Float[Array, " ..."]
Energy-dependent Lorentzian broadening in eV, same shape
as ``energy``.
Raises
------
ValueError
If ``config.mode`` is not one of ``"constant"``,
``"polynomial"``, or ``"tabulated"``.
Notes
-----
All three modes are fully JAX-differentiable with respect to
``config.coefficients``. The ``"constant"`` and ``"polynomial"``
modes are also differentiable with respect to ``energy``. The
``"tabulated"`` mode uses ``jnp.interp`` which has limited
gradient support (piecewise-constant gradients).
"""
mode: str = config.mode
if mode == "constant":
result: Float[Array, " ..."] = jnp.broadcast_to(
config.coefficients[0], energy.shape
)
return result
if mode == "polynomial":
result: Float[Array, " ..."] = jnp.polyval(config.coefficients, energy)
return result
if mode == "tabulated":
assert config.energy_nodes is not None
result: Float[Array, " ..."] = jnp.interp(
energy, config.energy_nodes, config.coefficients
)
return result
msg: str = f"Unknown self-energy mode: {mode}"
raise ValueError(msg)
__all__: list[str] = ["evaluate_self_energy"]