Coverage for dynasor / modes / qpoint.py: 92%

63 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-16 12:31 +0000

1import numpy as np 

2from numpy.typing import NDArray 

3 

4from ..units import radians_per_fs_to_THz 

5from .band import Band 

6 

7 

8class QPoint: 

9 """Representation of a single q-point and properties. 

10 

11 The bands can be accessed by, e.g., `qp[2]` to get band index 2 in the form 

12 of a :class:`~dynasor.modes.band.Band` object. 

13 

14 Parameters 

15 ---------- 

16 q 

17 q-point index. 

18 mp 

19 Mode project object. 

20 """ 

21 def __init__(self, q_index: int, mp): 

22 self._mp = mp 

23 self._q = q_index 

24 

25 def __str__(self): 

26 s = ['### q-point ###'] 

27 s += [f'Index: {self.index}'] 

28 s += [f'Reduced: {self.q_reduced}'] 

29 s += ['Cartesian: [' 

30 f'{self.q_cartesian[0]:.2f}, {self.q_cartesian[1]:.2f}, {self.q_cartesian[2]:.2f}' 

31 '] rad/Å'] 

32 s += [f'Wavenumber: {self.wavenumber:.2f} rad/Å'] 

33 s += [f'Wavelength: {self.wavelength:.2f} Å'] 

34 s += [f'q-minus: {self.q_minus.index} {self.q_minus.q_reduced}'] 

35 s += [f'Real: {self.is_real}'] 

36 s += [''] 

37 s += ['Omegas: [' 

38 ', '.join(f'{t * radians_per_fs_to_THz:.2f}' for t in self.omegas) + '] THz'] 

39 return '\n'.join(s) 

40 

41 def __repr__(self): 

42 return str(self) 

43 

44 def __getitem__(self, s): 

45 if s >= self._mp.primitive.n_atoms * 3: 

46 raise IndexError 

47 return Band(self.index, s, self._mp) 

48 

49 @property 

50 def q_minus(self): 

51 """The corresponding counter-propagating mode.""" 

52 return self._mp[self._mp.q_minus[self.index]] 

53 

54 @property 

55 def polarizations(self) -> NDArray[float]: 

56 """Polarization vectors for each band at this q-point, shape ``(Nb, Np, 3)``.""" 

57 return self._mp.polarizations[self.index] 

58 

59 @property 

60 def omegas(self) -> NDArray[float]: 

61 """Frequencies of each band at this q-point in rad/fs.""" 

62 return self._mp.omegas[self.index] 

63 

64 @property 

65 def is_real(self) -> bool: 

66 """If the q-point has purely real mode coordinates, `q=-q`.""" 

67 return self.index == self.q_minus.index 

68 

69 @property 

70 def index(self) -> int: 

71 """q-point index corresponding to :class:`~dynasor.ModeProjector.q_reduced`.""" 

72 return self._q 

73 

74 @property 

75 def wavenumber(self) -> float: 

76 """Wavenumber of mode in rad/Å. """ 

77 return np.linalg.norm(self._mp.q_cartesian[self.index]) 

78 

79 @property 

80 def wavelength(self) -> float: 

81 """Wavelength of mode in Å. """ 

82 return np.inf if self.wavenumber == 0 else 2 * np.pi / self.wavenumber 

83 

84 @property 

85 def q_reduced(self) -> NDArray[float]: 

86 """Reduced coordinates of this q-point.""" 

87 return self._mp.q_reduced[self.index] 

88 

89 @property 

90 def q_cartesian(self) -> NDArray[float]: 

91 """Cartesian coordinates of this q-point in rad/Å (2π included).""" 

92 return self._mp.q_cartesian[self.index] 

93 

94 @property 

95 def eigenmodes(self) -> NDArray[float]: 

96 """Eigenmodes in the supercell for each band at this q-point, shape ``(Nb, N, 3)``.""" 

97 return self._mp.eigenmodes[self.index] 

98 

99 @property 

100 def potential_energies(self) -> NDArray[float]: 

101 """Potential energy per band at this q-point as a ``(Nb,)`` array in eV.""" 

102 return self._mp.potential_energies[self.index] 

103 

104 @property 

105 def kinetic_energies(self) -> NDArray[float]: 

106 """Kinetic energy per band at this q-point as a ``(Nb,)`` array in eV.""" 

107 return self._mp.kinetic_energies[self.index] 

108 

109 @property 

110 def virial_energies(self) -> NDArray[float]: 

111 """Virial energy per band at this q-point as a ``(Nb,)`` array in eV.""" 

112 return self._mp.virial_energies[self.index]