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
« 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
5import numpy as np
7from ..units import radians_per_fs_to_THz
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
14class ComplexCoordinate:
15 """Class to work with the in general complex coordinates of lattice dynamics.
17 Can be cast to complex by `complex(cc)`
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
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
33 def __repr__(self):
34 return str(self)
36 def __complex__(self):
37 return complex(getattr(self._mp, 'get_' + self.__class__.__name__)()[self._q, self._s])
39 @property
40 def band(self) -> 'Band':
41 """The band to which this coordinate belongs"""
42 return self._mp[self._q][self._s]
44 @property
45 def complex(self):
46 """The complex coordinate as a complex number"""
47 return complex(self)
49 @complex.setter
50 def complex(self, C):
51 C = complex(C)
52 prop = self.__class__.__name__
54 val = getattr(self._mp, 'get_' + prop)()
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
62 val[self._q, self._s] = C
63 val[q2, self._s] = C.conjugate()
65 getattr(self._mp, 'set_' + prop)(val)
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)
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
79 @property
80 def amplitude(self):
81 """The amplitude of the complex coordinate"""
82 c = complex(self)
83 return np.abs(c)
85 @amplitude.setter
86 def amplitude(self, amplitude):
87 C = amplitude * np.exp(1j * self.phase)
88 self.complex = C
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
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
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