Source code for abinslib.bose

"""Bose occupation enum and calculations."""

from enum import Enum, auto

from euphonic import Quantity, ureg
import numpy as np


[docs] class NegativeTemperatureError(ValueError): """Bose factor is not meaningful for negative temperature."""
[docs] class BoseOccupation(Enum): """Occupation number for Bose-Einstein statistics. Typically we use 2N+1 in Debye-Waller factor (i.e. atomic displacements), N+1 for energy transfer to the sample and N for energy transfer from the sample. ONE is simply 1: i.e. unscaled temperature-insensitive occupation """ ONE = auto() N = auto() N_PLUS_ONE = auto() TWO_N_PLUS_ONE = auto()
[docs] def calculate_bose_factor( frequencies: Quantity, temperature: Quantity, occupation: BoseOccupation, ) -> np.array: """Get Bose factors corresponding to an array of frequency or energy. Args: frequencies: phonon frequencies, usually indexed (qpt, mode) temperature: determines magnitude of Bose factors occupation: typically N_PLUS_ONE is used for phonon excitations Returns: Bose factor array corresponding to input frequencies (i.e. usually indexed (qpt, mode)) """ if temperature == Quantity(0.0, "kelvin"): return _zero_t_bose_factor(frequencies, occupation) if temperature < Quantity(0.0, "kelvin"): raise NegativeTemperatureError("Temperature must not be negative") frequencies = frequencies.to("hartree").magnitude # Cast T to Kelvin first in case of non-multiplicative unit (celsius) kT = (ureg.k * temperature.to("kelvin")).to("hartree").magnitude two_n_plus_one = 1 / (np.tanh(frequencies / (2 * kT))) match occupation: case BoseOccupation.TWO_N_PLUS_ONE: return two_n_plus_one case BoseOccupation.N_PLUS_ONE: return two_n_plus_one * 0.5 + 0.5 case BoseOccupation.N: return two_n_plus_one * 0.5 - 0.5 case BoseOccupation.ONE: return np.ones_like(two_n_plus_one) case other: raise TypeError(f"Not a valid occupation number: {other}")
def _zero_t_bose_factor( frequencies: Quantity, occupation: BoseOccupation ) -> np.ndarray: """Get ideal occupation values if T=0, avoiding divide-by-zero.""" match occupation: case BoseOccupation.N_PLUS_ONE | BoseOccupation.TWO_N_PLUS_ONE: return np.ones_like(frequencies.magnitude) case BoseOccupation.ONE: return np.ones_like(frequencies.magnitude) case BoseOccupation.N: return np.zeros_like(frequencies.magnitude) case other: raise TypeError(f"Not a valid occupation number: {other}")