Coverage for dynasor/modes/complex_coordinate.py: 97%
76 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 20:02 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 20:02 +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: # pragma: no cover
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 Concrete subclasses :class:`Q`, :class:`P`, and :class:`F` represent the
20 mode coordinate, momentum, and force respectively, and are accessed via
21 :attr:`~dynasor.modes.band.Band.Q`, :attr:`~dynasor.modes.band.Band.P`,
22 and :attr:`~dynasor.modes.band.Band.F` on a :class:`~dynasor.modes.band.Band` object.
24 Example
25 -------
26 >>> # Access a coordinate via a Band object; mp is a ModeProjector instance,
27 >>> # mp[0] selects q-point 0 and [1] selects band 1 within that q-point.
28 >>> band = mp[0][1] # doctest: +SKIP
29 >>> # The coordinate can be set directly as a complex number,
30 >>> # or via its amplitude and phase independently.
31 >>> band.Q.complex = 1.7j # doctest: +SKIP
32 >>> band.Q.amplitude = 3.8 # doctest: +SKIP
33 >>> band.Q.phase = 2*np.pi # doctest: +SKIP
34 """
36 def __init__(self, q, s, mp):
37 """The complex coordinate belongs to a q-point and band."""
38 self._q = q
39 self._s = s
40 self._mp = mp
42 def __str__(self):
43 return f'{self.__class__.__name__}(q={self._q}, s={self._s})'
45 def __repr__(self):
46 return str(self)
48 def __complex__(self):
49 return complex(getattr(self._mp, 'get_' + self.__class__.__name__)()[self._q, self._s])
51 @property
52 def band(self) -> 'Band':
53 """The band to which this coordinate belongs to."""
54 return self._mp[self._q][self._s]
56 @property
57 def complex(self):
58 """The complex coordinate as a complex number."""
59 return complex(self)
61 @complex.setter
62 def complex(self, C):
63 C = complex(C)
64 prop = self.__class__.__name__
66 val = getattr(self._mp, 'get_' + prop)()
68 if self._mp[self._q].is_real: 68 ↛ 72line 68 didn't jump to line 72 because the condition on line 68 was always true
69 assert np.isclose(C.imag, 0)
70 q2 = self._q
71 else:
72 q2 = self._mp[self._q].q_minus.index
74 val[self._q, self._s] = C
75 val[q2, self._s] = C.conjugate()
77 getattr(self._mp, 'set_' + prop)(val)
79 @property
80 def phase(self):
81 """The phase of the complex coordinate."""
82 c = complex(self)
83 return math.atan2(c.imag, c.real)
85 @phase.setter
86 def phase(self, phase):
87 C = complex(self)
88 C = np.abs(C) * np.exp(1j * phase)
89 self.complex = C
91 @property
92 def amplitude(self):
93 """The amplitude of the complex coordinate."""
94 c = complex(self)
95 return np.abs(c)
97 @amplitude.setter
98 def amplitude(self, amplitude):
99 C = amplitude * np.exp(1j * self.phase)
100 self.complex = C
103class Q(ComplexCoordinate):
104 def __str__(self):
105 s = ['### Mode coordinate Q ###']
106 s += [f'band: [{self.band.index}] {self.band.omega * radians_per_fs_to_THz:.2f} THz']
107 s += [f'q-point: [{self.band.qpoint.index}] {self.band.qpoint.q_reduced}']
108 s += [f'Value: {self.complex:.2f} Å√dmu']
109 s += [f'Amplitude: {self.amplitude:.2f} Å√dmu']
110 s += [f'Phase: {self.phase:.2f} radians']
111 return '\n'.join(s)
114class P(ComplexCoordinate):
115 def __str__(self):
116 s = ['### Mode momentum P ###']
117 s += [f'band: [{self.band.index}] {self.band.omega * radians_per_fs_to_THz:.2f} THz']
118 s += [f'q-point: [{self.band.qpoint.index}] {self.band.qpoint.q_reduced}']
119 s += [f'Value: {self.complex:.2f} Å√dmu/fs']
120 s += [f'Amplitude: {self.amplitude:.2f} Å√dmu/fs']
121 s += [f'Phase: {self.phase:.2f} radians']
122 return '\n'.join(s)
125class F(ComplexCoordinate):
126 def __str__(self):
127 s = ['### Mode force F ###']
128 s += [f'band: [{self.band.index}] {self.band.omega * radians_per_fs_to_THz:.2f} THz']
129 s += [f'q-point: [{self.band.qpoint.index}] {self.band.qpoint.q_reduced}']
130 s += [f'Value: {self.complex:.2f} Å√dmu/fs²']
131 s += [f'Amplitude: {self.amplitude:.2f} Å√dmu/fs²']
132 s += [f'Phase: {self.phase:.2f} radians']
133 return '\n'.join(s)