Coverage for strongcoca/response/excitations.py: 100%

50 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-10-26 18:44 +0000

1import numpy as np 

2 

3from .base import BaseResponse 

4from ..types import Array 

5from ..units import au_to_eV, eV_to_au, au_to_eA, eA_to_au 

6from .utilities import Broadening, NoArtificialBroadening, broaden 

7 

8 

9class Excitations(BaseResponse): 

10 """Objects of this class represent discrete excitation spectra. 

11 

12 Parameters 

13 ---------- 

14 energies 

15 Excitation energies; array with shape {n}. 

16 

17 Units are eV by default, optionally atomic units (see :attr:`units`). 

18 transition_dipole_moments 

19 Transition dipole moments of the excitations; array with shape {n}x{3}. 

20 

21 Units are eÅ by default, optionally atomic units (see :attr:`units`). 

22 broadening 

23 Artificial broadening used for the continuous response; 

24 defaults to no broadening. 

25 units 

26 `eVA` to specify energies in eV and transition_dipole_moments in eÅ 

27 or `au` to specify inputs in atomic units. 

28 

29 This parameter determines whether conversion should be performed during 

30 initialization and has no effect on instance methods and variables. 

31 name 

32 Name of response. 

33 """ 

34 def __init__(self, 

35 energies: np.ndarray, 

36 transition_dipole_moments: np.ndarray, 

37 broadening: Broadening = NoArtificialBroadening(), 

38 units: str = 'eVA', 

39 name: str = 'Excitations') -> None: 

40 super().__init__(broadening=broadening, pbc=False, name=name) 

41 

42 if units == 'eVA': 

43 energies = energies * eV_to_au 

44 transition_dipole_moments = transition_dipole_moments * eA_to_au 

45 elif units != 'au': 

46 raise ValueError(f"units has to be 'eVA' or 'au', not '{units}'") 

47 

48 self._energies = energies 

49 self._transition_dipole_moments = transition_dipole_moments 

50 

51 @property 

52 def energies(self) -> np.ndarray: 

53 """Excitation energies in units of eV; array with shape {n}.""" 

54 return self._energies * au_to_eV # type: ignore 

55 

56 @property 

57 def transition_dipole_moments(self) -> np.ndarray: 

58 """Transition dipole moments of the excitations in units of eÅ; 

59 array with shape {n}x{3}. 

60 """ 

61 return self._transition_dipole_moments * au_to_eA # type: ignore 

62 

63 @property 

64 def oscillator_strengths(self) -> np.ndarray: 

65 """Unitless oscillator strengths of the excitations; 

66 array with shape {n}. 

67 """ 

68 osc_nv = self.oscillator_strength_vectors 

69 osc_n = np.average(osc_nv, axis=1) 

70 return osc_n # type: ignore 

71 

72 @property 

73 def oscillator_strength_vectors(self) -> np.ndarray: 

74 """Unitless oscillator strength vectors of the excitations; 

75 array with shape {n}x3. 

76 """ 

77 omega_n = self._energies 

78 mu_nv = self._transition_dipole_moments 

79 osc_nv = 2 * omega_n[:, np.newaxis] * mu_nv**2 

80 return osc_nv # type: ignore 

81 

82 @property 

83 def oscillator_strength_tensors(self) -> np.ndarray: 

84 """Unitless oscillator strength tensors of the excitations; 

85 array with shape {n}x3x3. 

86 """ 

87 omega_n = self._energies 

88 mu_nv = self._transition_dipole_moments 

89 osc_nvv = 2 * np.einsum('n,nx,ny->nxy', omega_n, mu_nv, mu_nv, optimize=True) 

90 return osc_nvv # type: ignore 

91 

92 def _get_dynamic_polarizability(self, frequencies: Array) -> np.ndarray: 

93 freq_w = np.asarray(frequencies) 

94 omega_I = self._energies 

95 osc_Ivv = self.oscillator_strength_tensors 

96 dm_wvv = broaden(omega_I, osc_Ivv, freq_w, broadening=self._broadening) 

97 return dm_wvv 

98 

99 def _get_dynamic_polarizability_imaginary_frequency( 

100 self, frequencies: Array) -> np.ndarray: 

101 freq_w = np.asarray(frequencies) 

102 omega_I = self._energies 

103 osc_Ivv = self.oscillator_strength_tensors 

104 dm_wvv = broaden(omega_I, osc_Ivv, freq_w, freq_axis='imag') 

105 return dm_wvv