Coverage for local_installation/dynasor/modes/complex_coordinate.py: 46%

61 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 07:34 +0000

1from __future__ import annotations # for 3.9 for typehint 'Band' in cc.band 

2from typing import TYPE_CHECKING 

3import math 

4 

5import numpy as np 

6 

7from ..units import radians_per_fs_to_THz 

8 

9 

10if TYPE_CHECKING: 10 ↛ 11line 10 didn't jump to line 11 because the condition on line 10 was never true

11 from .band import Band 

12 

13 

14class ComplexCoordinate: 

15 """Class to work with the in general complex coordinates of lattice dynamics. 

16 

17 Can be cast to complex by `complex(cc)` 

18 

19 Example 

20 ------- 

21 >>> cc.complex = 1.7j # doctest: +SKIP 

22 >>> cc.amplitude = 3.8 # doctest: +SKIP 

23 >>> cc.phase = 2*np.pi # doctest: +SKIP 

24 

25 

26 """ 

27 def __init__(self, q, s, mp): 

28 """The complex coordinate belongs to a q-point and band""" 

29 self._q = q 

30 self._s = s 

31 self._mp = mp 

32 

33 def __repr__(self): 

34 return str(self) 

35 

36 def __complex__(self): 

37 return complex(getattr(self._mp, 'get_' + self.__class__.__name__)()[self._q, self._s]) 

38 

39 @property 

40 def band(self) -> 'Band': 

41 """The band to which this coordinate belongs""" 

42 return self._mp[self._q][self._s] 

43 

44 @property 

45 def complex(self): 

46 """The complex coordinate as a complex number""" 

47 return complex(self) 

48 

49 @complex.setter 

50 def complex(self, C): 

51 C = complex(C) 

52 prop = self.__class__.__name__ 

53 

54 val = getattr(self._mp, 'get_' + prop)() 

55 

56 if self._mp[self._q].is_real: 

57 assert np.isclose(C.imag, 0) 

58 q2 = self._q 

59 else: 

60 q2 = self._mp[self._q].q_minus.index 

61 

62 val[self._q, self._s] = C 

63 val[q2, self._s] = C.conjugate() 

64 

65 getattr(self._mp, 'set_' + prop)(val) 

66 

67 @property 

68 def phase(self): 

69 """The phase of the complex coordinate""" 

70 c = complex(self) 

71 return math.atan2(c.imag, c.real) 

72 

73 @phase.setter 

74 def phase(self, phase): 

75 C = complex(self) 

76 C = np.abs(C) * np.exp(1j * phase) 

77 self.complex = C 

78 

79 @property 

80 def amplitude(self): 

81 """The amplitude of the complex coordinate""" 

82 c = complex(self) 

83 return np.abs(c) 

84 

85 @amplitude.setter 

86 def amplitude(self, amplitude): 

87 C = amplitude * np.exp(1j * self.phase) 

88 self.complex = C 

89 

90 

91class Q(ComplexCoordinate): 

92 def __str__(self): 

93 string = f"""### Mode coordinate Q ### 

94band: [{self.band.index}] {self.band.omega * radians_per_fs_to_THz:.2f} THz 

95q-point: [{self.band.qpoint.index}] {self.band.qpoint.q_reduced} 

96Value: {self.complex:.2f} Å√dmu 

97Amplitude: {self.amplitude:.2f} Å√dmu 

98Phase: {self.phase:.2f} radians 

99""" 

100 return string 

101 

102 

103class P(ComplexCoordinate): 

104 def __str__(self): 

105 string = f"""### Mode momentum P ### 

106band: [{self.band.index}] {self.band.omega * radians_per_fs_to_THz:.2f} THz 

107q-point: [{self.band.qpoint.index}] {self.band.qpoint.q_reduced} 

108Value: {self.complex:.2f} Å√dmu/fs 

109Amplitude: {self.amplitude:.2f} Å√dmu/fs 

110Phase: {self.phase:.2f} radians 

111""" 

112 return string 

113 

114 

115class F(ComplexCoordinate): 

116 def __str__(self): 

117 string = f"""### Mode force F ### 

118band: [{self.band.index}] {self.band.omega * radians_per_fs_to_THz:.2f} THz 

119q-point: [{self.band.qpoint.index}] {self.band.qpoint.q_reduced} 

120Value: {self.complex:.2f} Å√dmu/fs² 

121Amplitude: {self.amplitude:.2f} Å√dmu/fs² 

122Phase: {self.phase:.2f} radians 

123""" 

124 return string