TB2J 0.9.9.4__py3-none-any.whl → 0.9.9.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- TB2J/Jdownfolder.py +3 -2
- TB2J/MAEGreen.py +22 -17
- TB2J/__init__.py +1 -1
- TB2J/exchange_params.py +1 -2
- TB2J/interfaces/abacus/gen_exchange_abacus.py +3 -0
- TB2J/interfaces/siesta_interface.py +15 -7
- TB2J/io_exchange/__init__.py +2 -0
- TB2J/io_exchange/io_exchange.py +40 -12
- TB2J/magnon/__init__.py +3 -0
- TB2J/magnon/io_exchange2.py +695 -0
- TB2J/magnon/magnon3.py +334 -0
- TB2J/magnon/magnon_io.py +48 -0
- TB2J/magnon/magnon_math.py +53 -0
- TB2J/magnon/plot.py +58 -0
- TB2J/magnon/structure.py +348 -0
- TB2J/mathutils/__init__.py +2 -0
- TB2J/mathutils/fibonacci_sphere.py +1 -1
- TB2J/mathutils/rotate_spin.py +0 -3
- TB2J/symmetrize_J.py +1 -1
- tb2j-0.9.9.6.data/scripts/TB2J_magnon2.py +78 -0
- {tb2j-0.9.9.4.dist-info → tb2j-0.9.9.6.dist-info}/METADATA +1 -2
- {tb2j-0.9.9.4.dist-info → tb2j-0.9.9.6.dist-info}/RECORD +36 -28
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_downfold.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_eigen.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_magnon.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_magnon_dos.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_merge.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_rotate.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/TB2J_rotateDM.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/abacus2J.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/siesta2J.py +0 -0
- {tb2j-0.9.9.4.data → tb2j-0.9.9.6.data}/scripts/wann2J.py +0 -0
- {tb2j-0.9.9.4.dist-info → tb2j-0.9.9.6.dist-info}/WHEEL +0 -0
- {tb2j-0.9.9.4.dist-info → tb2j-0.9.9.6.dist-info}/entry_points.txt +0 -0
- {tb2j-0.9.9.4.dist-info → tb2j-0.9.9.6.dist-info}/licenses/LICENSE +0 -0
- {tb2j-0.9.9.4.dist-info → tb2j-0.9.9.6.dist-info}/top_level.txt +0 -0
TB2J/magnon/magnon3.py
ADDED
@@ -0,0 +1,334 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
|
3
|
+
import numpy as np
|
4
|
+
from scipy.spatial.transform import Rotation
|
5
|
+
|
6
|
+
from ..io_exchange import SpinIO
|
7
|
+
from ..mathutils import Hermitize, get_rotation_arrays
|
8
|
+
from .plot import BandsPlot
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class Magnon:
|
13
|
+
"""
|
14
|
+
Magnon calculator implementation using dataclass
|
15
|
+
"""
|
16
|
+
|
17
|
+
nspin: int
|
18
|
+
# ind_atoms: list
|
19
|
+
magmom: np.ndarray
|
20
|
+
Rlist: np.ndarray
|
21
|
+
JR: np.ndarray
|
22
|
+
_Q: np.ndarray = np.array([0.0, 0.0, 0.0], dtype=float)
|
23
|
+
_uz: np.ndarray = np.array([[0.0, 0.0, 1.0]], dtype=float)
|
24
|
+
_n: np.ndarray = np.array([0, 0, 1], dtype=float)
|
25
|
+
|
26
|
+
def set_reference(self, Q, uz, n):
|
27
|
+
"""
|
28
|
+
Set reference propagation vector and quantization axis
|
29
|
+
|
30
|
+
Parameters
|
31
|
+
----------
|
32
|
+
Q : array_like
|
33
|
+
Propagation vector
|
34
|
+
uz : array_like
|
35
|
+
Quantization axis
|
36
|
+
n : array_like
|
37
|
+
Normal vector for rotation
|
38
|
+
"""
|
39
|
+
self.set_propagation_vector(Q)
|
40
|
+
self._uz = np.array(uz, dtype=float)
|
41
|
+
self._n = np.array(n, dtype=float)
|
42
|
+
|
43
|
+
def set_propagation_vector(self, Q):
|
44
|
+
"""Set propagation vector"""
|
45
|
+
self._Q = np.array(Q)
|
46
|
+
|
47
|
+
@property
|
48
|
+
def Q(self):
|
49
|
+
"""Get propagation vector"""
|
50
|
+
if self._Q is None:
|
51
|
+
raise ValueError("Propagation vector Q is not set.")
|
52
|
+
return self._Q
|
53
|
+
|
54
|
+
@Q.setter
|
55
|
+
def Q(self, value):
|
56
|
+
if not isinstance(value, (list, np.ndarray)):
|
57
|
+
raise TypeError("Propagation vector Q must be a list or numpy array.")
|
58
|
+
if len(value) != 3:
|
59
|
+
raise ValueError("Propagation vector Q must have three components.")
|
60
|
+
self._Q = np.array(value)
|
61
|
+
|
62
|
+
def Jq(self, kpoints):
|
63
|
+
"""
|
64
|
+
Compute the exchange interactions in reciprocal space.
|
65
|
+
|
66
|
+
The exchange interactions J(q) are computed using the Fourier transform:
|
67
|
+
J(q) = ∑_R J(R) exp(iq·R)
|
68
|
+
|
69
|
+
Array shapes and indices:
|
70
|
+
- kpoints: (nkpt, 3) array of k-points
|
71
|
+
- Rlist: (nR, 3) array of real-space lattice vectors
|
72
|
+
- JR: (nR, nspin, nspin, 3, 3) array of exchange tensors in real space
|
73
|
+
where nspin is number of magnetic atoms
|
74
|
+
- Output Jq: (nkpt, nspin, nspin, 3, 3) array of exchange tensors in q-space
|
75
|
+
|
76
|
+
If propagation vector Q is set, each J(R) is rotated before the Fourier transform:
|
77
|
+
J'_mn(R) = R_m(ϕ)^T J(R) R_n(ϕ)
|
78
|
+
where ϕ = 2π R·Q and R(ϕ) is rotation matrix around axis n by angle ϕ
|
79
|
+
|
80
|
+
Parameters
|
81
|
+
----------
|
82
|
+
kpoints : array_like (nkpt, 3)
|
83
|
+
k-points at which to evaluate the exchange interactions
|
84
|
+
|
85
|
+
Returns
|
86
|
+
-------
|
87
|
+
numpy.ndarray (nkpt, nspin, nspin, 3, 3)
|
88
|
+
Exchange interaction tensors J(q) at each k-point
|
89
|
+
First two indices are for magnetic atom pairs
|
90
|
+
Last two indices are for 3x3 tensor components
|
91
|
+
"""
|
92
|
+
Rlist = np.array(self.Rlist)
|
93
|
+
JR = self.JR
|
94
|
+
JRprime = JR.copy()
|
95
|
+
|
96
|
+
for iR, R in enumerate(Rlist):
|
97
|
+
if self._Q is not None:
|
98
|
+
# Rotate exchange tensors based on propagation vector
|
99
|
+
phi = 2 * np.pi * R @ self._Q # angle ϕ = 2π R·Q
|
100
|
+
rv = phi * self._n # rotation vector
|
101
|
+
Rmat = Rotation.from_rotvec(rv).as_matrix()
|
102
|
+
# J'_mn(R) = R_m(ϕ)^T J(R) R_n(ϕ) using Einstein summation.
|
103
|
+
# Here m is always in the R=0, thus the rotation is only applied on the
|
104
|
+
# n , so only on the right.
|
105
|
+
JRprime[iR] = np.einsum(" rijxy, yb -> rijab", JR[iR], Rmat)
|
106
|
+
|
107
|
+
nkpt = kpoints.shape[0]
|
108
|
+
Jq = np.zeros((nkpt, self.nspin, self.nspin, 3, 3), dtype=complex)
|
109
|
+
|
110
|
+
for iR, R in enumerate(Rlist):
|
111
|
+
for iqpt, qpt in enumerate(kpoints):
|
112
|
+
# Fourier transform of exchange tensors
|
113
|
+
phase = 2 * np.pi * R @ qpt
|
114
|
+
Jq[iqpt] += np.exp(1j * phase) * JRprime[iR]
|
115
|
+
|
116
|
+
# Ensure Hermiticity: J(q) = J(-q)†
|
117
|
+
for iqpt in range(nkpt):
|
118
|
+
Jq[iqpt, :, :, :, :] += np.conj(
|
119
|
+
np.moveaxis(Jq[iqpt, :, :, :, :], [1, 3], [2, 4])
|
120
|
+
)
|
121
|
+
Jq[iqpt, :, :, :, :] /= 2.0
|
122
|
+
return Jq
|
123
|
+
|
124
|
+
def Hq(self, kpoints, anisotropic=True):
|
125
|
+
"""
|
126
|
+
Compute the magnon Hamiltonian in reciprocal space.
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
kpoints : array_like
|
131
|
+
k-points at which to evaluate the Hamiltonian
|
132
|
+
anisotropic : bool, optional
|
133
|
+
Whether to include anisotropic interactions, default True
|
134
|
+
|
135
|
+
Returns
|
136
|
+
-------
|
137
|
+
numpy.ndarray
|
138
|
+
Magnon Hamiltonian matrix at each k-point
|
139
|
+
"""
|
140
|
+
magmoms = self.magmom.copy()
|
141
|
+
magmoms /= np.linalg.norm(magmoms, axis=-1)[:, None]
|
142
|
+
|
143
|
+
U, V = get_rotation_arrays(magmoms, u=self._uz)
|
144
|
+
|
145
|
+
J0 = self.Jq(np.zeros((1, 3)), anisotropic=anisotropic)
|
146
|
+
J0 = -Hermitize(J0)[:, :, 0]
|
147
|
+
Jq = -Hermitize(self.Jq(kpoints, anisotropic=anisotropic))
|
148
|
+
|
149
|
+
C = np.diag(np.einsum("ix,ijxy,jy->i", V, 2 * J0, V))
|
150
|
+
B = np.einsum("ix,ijkxy,jy->kij", U, Jq, U)
|
151
|
+
A1 = np.einsum("ix,ijkxy,jy->kij", U, Jq, U.conj())
|
152
|
+
A2 = np.einsum("ix,ijkxy,jy->kij", U.conj(), Jq, U)
|
153
|
+
|
154
|
+
return np.block([[A1 - C, B], [B.swapaxes(-1, -2).conj(), A2 - C]])
|
155
|
+
|
156
|
+
def _magnon_energies(self, kpoints, anisotropic=True, u=None):
|
157
|
+
"""Calculate magnon energies"""
|
158
|
+
H = self.Hq(kpoints, anisotropic=anisotropic)
|
159
|
+
n = H.shape[-1] // 2
|
160
|
+
I = np.eye(n)
|
161
|
+
|
162
|
+
min_eig = 0.0
|
163
|
+
try:
|
164
|
+
K = np.linalg.cholesky(H)
|
165
|
+
except np.linalg.LinAlgError:
|
166
|
+
try:
|
167
|
+
K = np.linalg.cholesky(H + 1e-6 * np.eye(2 * n))
|
168
|
+
except np.linalg.LinAlgError:
|
169
|
+
from warnings import warn
|
170
|
+
|
171
|
+
min_eig = np.min(np.linalg.eigvalsh(H))
|
172
|
+
K = np.linalg.cholesky(H - (min_eig - 1e-6) * np.eye(2 * n))
|
173
|
+
warn(
|
174
|
+
f"WARNING: The system may be far from the magnetic ground-state. Minimum eigenvalue: {min_eig}. The magnon energies might be unphysical."
|
175
|
+
)
|
176
|
+
|
177
|
+
g = np.block([[1 * I, 0 * I], [0 * I, -1 * I]])
|
178
|
+
KH = K.swapaxes(-1, -2).conj()
|
179
|
+
|
180
|
+
return np.linalg.eigvalsh(KH @ g @ K)[:, n:] + min_eig
|
181
|
+
|
182
|
+
def get_magnon_bands(
|
183
|
+
self,
|
184
|
+
kpoints: np.array = np.array([]),
|
185
|
+
path: str = None,
|
186
|
+
npoints: int = 300,
|
187
|
+
special_points: dict = None,
|
188
|
+
tol: float = 2e-4,
|
189
|
+
pbc: tuple = None,
|
190
|
+
cartesian: bool = False,
|
191
|
+
labels: list = None,
|
192
|
+
anisotropic: bool = True,
|
193
|
+
u: np.array = None,
|
194
|
+
):
|
195
|
+
"""Get magnon band structure"""
|
196
|
+
pbc = self._pbc if pbc is None else pbc
|
197
|
+
u = self._uz if u is None else u
|
198
|
+
if kpoints.size == 0:
|
199
|
+
from ase.cell import Cell
|
200
|
+
|
201
|
+
bandpath = Cell(self._cell).bandpath(
|
202
|
+
path=path,
|
203
|
+
npoints=npoints,
|
204
|
+
special_points=special_points,
|
205
|
+
eps=tol,
|
206
|
+
pbc=pbc,
|
207
|
+
)
|
208
|
+
kpoints = bandpath.kpts
|
209
|
+
spk = bandpath.special_points
|
210
|
+
spk[r"$\Gamma$"] = spk.pop("G", np.zeros(3))
|
211
|
+
labels = [
|
212
|
+
(i, symbol)
|
213
|
+
for symbol in spk
|
214
|
+
for i in np.where((kpoints == spk[symbol]).all(axis=1))[0]
|
215
|
+
]
|
216
|
+
elif cartesian:
|
217
|
+
kpoints = np.linalg.solve(self._cell.T, kpoints.T).T
|
218
|
+
|
219
|
+
bands = self._magnon_energies(kpoints, anisotropic=anisotropic)
|
220
|
+
|
221
|
+
return labels, bands
|
222
|
+
|
223
|
+
def plot_magnon_bands(self, **kwargs):
|
224
|
+
"""
|
225
|
+
Plot magnon band structure.
|
226
|
+
|
227
|
+
Parameters
|
228
|
+
----------
|
229
|
+
**kwargs
|
230
|
+
Additional keyword arguments passed to get_magnon_bands and plotting functions
|
231
|
+
"""
|
232
|
+
filename = kwargs.pop("filename", None)
|
233
|
+
kpath, bands = self.get_magnon_bands(**kwargs)
|
234
|
+
bands_plot = BandsPlot(bands, kpath)
|
235
|
+
bands_plot.plot(filename=filename)
|
236
|
+
|
237
|
+
@classmethod
|
238
|
+
def load_from_io(cls, exc: SpinIO, **kwargs):
|
239
|
+
"""
|
240
|
+
Create Magnon instance from SpinIO
|
241
|
+
|
242
|
+
Parameters
|
243
|
+
----------
|
244
|
+
exc : SpinIO
|
245
|
+
SpinIO instance with exchange parameters
|
246
|
+
**kwargs : dict
|
247
|
+
Additional arguments passed to get_full_Jtensor_for_Rlist
|
248
|
+
|
249
|
+
Returns
|
250
|
+
-------
|
251
|
+
Magnon
|
252
|
+
Initialized Magnon instance
|
253
|
+
"""
|
254
|
+
return cls(
|
255
|
+
nspin=exc.nspin,
|
256
|
+
magmom=exc.magmoms,
|
257
|
+
Rlist=exc.Rlist,
|
258
|
+
JR=exc.get_full_Jtensor_for_Rlist(order="ij33", **kwargs),
|
259
|
+
)
|
260
|
+
|
261
|
+
@classmethod
|
262
|
+
def from_TB2J_results(cls, path=None, fname="TB2J.pickle", **kwargs):
|
263
|
+
"""
|
264
|
+
Create Magnon instance from TB2J results.
|
265
|
+
|
266
|
+
Parameters
|
267
|
+
----------
|
268
|
+
path : str, optional
|
269
|
+
Path to the TB2J results file
|
270
|
+
fname : str, optional
|
271
|
+
Filename of the TB2J results file, default "TB2J.pickle"
|
272
|
+
**kwargs : dict
|
273
|
+
Additional arguments passed to load_from_io
|
274
|
+
|
275
|
+
Returns
|
276
|
+
-------
|
277
|
+
Magnon
|
278
|
+
Initialized Magnon instance
|
279
|
+
"""
|
280
|
+
exc = SpinIO.load_pickle(path=path, fname=fname)
|
281
|
+
return cls.load_from_io(exc, **kwargs)
|
282
|
+
|
283
|
+
|
284
|
+
def test_magnon(path="TB2J_results"):
|
285
|
+
"""Test the magnon calculator by loading from TB2J_results and computing at high-symmetry points."""
|
286
|
+
from pathlib import Path
|
287
|
+
|
288
|
+
import numpy as np
|
289
|
+
|
290
|
+
# Check if TB2J_results exists
|
291
|
+
results_path = Path(path)
|
292
|
+
if not results_path.exists():
|
293
|
+
raise FileNotFoundError(f"TB2J_results directory not found at {path}")
|
294
|
+
|
295
|
+
# Load magnon calculator from TB2J results
|
296
|
+
print(f"Loading exchange parameters from {path}...")
|
297
|
+
magnon = Magnon.from_TB2J_results(path=path, iso_only=True)
|
298
|
+
|
299
|
+
# Define high-symmetry points for a cube
|
300
|
+
kpoints = np.array(
|
301
|
+
[
|
302
|
+
[0.0, 0.0, 0.0], # Γ (Gamma)
|
303
|
+
[0.5, 0.0, 0.0], # X
|
304
|
+
[0.5, 0.5, 0.0], # M
|
305
|
+
[0.5, 0.5, 0.5], # R
|
306
|
+
]
|
307
|
+
)
|
308
|
+
klabels = ["Gamma", "X", "M", "R"]
|
309
|
+
|
310
|
+
print("\nComputing exchange interactions at high-symmetry points...")
|
311
|
+
Jq = magnon.Jq(kpoints)
|
312
|
+
|
313
|
+
print(f"\nResults for {len(kpoints)} k-points:")
|
314
|
+
print("-" * 50)
|
315
|
+
print("Exchange interactions J(q):")
|
316
|
+
print(f"Shape of Jq tensor: {Jq.shape}")
|
317
|
+
print(
|
318
|
+
f"Dimensions: (n_kpoints={Jq.shape[0]}, n_spin={Jq.shape[1]}, n_spin={Jq.shape[2]}, xyz={Jq.shape[3]}, xyz={Jq.shape[4]})"
|
319
|
+
)
|
320
|
+
|
321
|
+
print("\nComputing magnon energies...")
|
322
|
+
energies = magnon._magnon_energies(kpoints)
|
323
|
+
|
324
|
+
print("\nMagnon energies at high-symmetry points (in meV):")
|
325
|
+
print("-" * 50)
|
326
|
+
for i, (k, label) in enumerate(zip(kpoints, klabels)):
|
327
|
+
print(f"\n{label}-point k={k}:")
|
328
|
+
print(f"Energies: {energies[i] * 1000:.3f} meV") # Convert to meV
|
329
|
+
|
330
|
+
return magnon, Jq, energies
|
331
|
+
|
332
|
+
|
333
|
+
if __name__ == "__main__":
|
334
|
+
test_magnon()
|
TB2J/magnon/magnon_io.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
from TB2J.io_exchange import SpinIO
|
4
|
+
|
5
|
+
|
6
|
+
class MagnonIO:
|
7
|
+
"""Handle IO operations for magnon calculations"""
|
8
|
+
|
9
|
+
def __init__(self, exc: SpinIO):
|
10
|
+
"""
|
11
|
+
Initialize MagnonIO with a SpinIO instance
|
12
|
+
|
13
|
+
Parameters
|
14
|
+
----------
|
15
|
+
exc : SpinIO
|
16
|
+
SpinIO instance containing exchange information
|
17
|
+
"""
|
18
|
+
self.exc = exc
|
19
|
+
|
20
|
+
def get_nspin(self):
|
21
|
+
"""Get number of spins"""
|
22
|
+
return self.exc.get_nspin()
|
23
|
+
|
24
|
+
def get_ind_atoms(self):
|
25
|
+
"""Get atom indices"""
|
26
|
+
return self.exc.ind_atoms
|
27
|
+
|
28
|
+
def get_magmom(self):
|
29
|
+
"""Get magnetic moments"""
|
30
|
+
nspin = self.get_nspin()
|
31
|
+
return np.array([self.exc.spinat[self.exc.iatom(i)] for i in range(nspin)])
|
32
|
+
|
33
|
+
def get_rlist(self):
|
34
|
+
"""Get R-vectors list"""
|
35
|
+
return self.exc.Rlist
|
36
|
+
|
37
|
+
def get_jtensor(self, asr=False, iso_only=False):
|
38
|
+
"""
|
39
|
+
Get full J tensor for R-list
|
40
|
+
|
41
|
+
Parameters
|
42
|
+
----------
|
43
|
+
asr : bool, optional
|
44
|
+
Acoustic sum rule, default False
|
45
|
+
iso_only : bool, optional
|
46
|
+
Only isotropic interactions, default False
|
47
|
+
"""
|
48
|
+
return self.exc.get_full_Jtensor_for_Rlist(asr=asr, iso_only=iso_only)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
__all__ = ["generate_grid", "get_rotation_arrays", "round_to_precision", "uz", "I"]
|
4
|
+
|
5
|
+
I = np.eye(3)
|
6
|
+
uz = np.array([[0.0, 0.0, 1.0]])
|
7
|
+
|
8
|
+
|
9
|
+
def generate_grid(kmesh, sort=True):
|
10
|
+
half_grid = [int(n / 2) for n in kmesh]
|
11
|
+
grid = np.stack(
|
12
|
+
np.meshgrid(*[np.arange(-n, n + 1) for n in half_grid]), axis=-1
|
13
|
+
).reshape(-1, 3)
|
14
|
+
|
15
|
+
if sort:
|
16
|
+
idx = np.linalg.norm(grid, axis=-1).argsort()
|
17
|
+
grid = grid[idx]
|
18
|
+
|
19
|
+
return grid
|
20
|
+
|
21
|
+
|
22
|
+
# def JR_to_Jq(JR, Rlist, qpt, vecn):
|
23
|
+
# for iR, R in enumerate(Rlist):
|
24
|
+
# phase = 2 * np.pi * R @ qpt
|
25
|
+
# rv = phase * vecn
|
26
|
+
# Rot = Rotation.from_rotvec(rv.reshape(-1, 3)).as_matrix().reshape(R.shape[0], 3, 3)
|
27
|
+
#
|
28
|
+
|
29
|
+
|
30
|
+
def get_rotation_arrays(magmoms, u=uz):
|
31
|
+
dim = magmoms.shape[0]
|
32
|
+
v = magmoms
|
33
|
+
n = np.cross(u, v)
|
34
|
+
n /= np.linalg.norm(n, axis=-1).reshape(dim, 1)
|
35
|
+
z = np.repeat(u, dim, axis=0)
|
36
|
+
A = np.stack([z, np.cross(n, z), n], axis=1)
|
37
|
+
B = np.stack([v, np.cross(n, v), n], axis=1)
|
38
|
+
R = np.einsum("nki,nkj->nij", A, B)
|
39
|
+
|
40
|
+
Rnan = np.isnan(R)
|
41
|
+
if Rnan.any():
|
42
|
+
nanidx = np.where(Rnan)[0]
|
43
|
+
R[nanidx] = I
|
44
|
+
R[nanidx, 2] = v[nanidx]
|
45
|
+
|
46
|
+
U = R[:, 0] + 1j * R[:, 1]
|
47
|
+
V = R[:, 2]
|
48
|
+
|
49
|
+
return U, V
|
50
|
+
|
51
|
+
|
52
|
+
def round_to_precision(array, precision):
|
53
|
+
return precision * np.round(array / precision)
|
TB2J/magnon/plot.py
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
import matplotlib.pyplot as plt
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
|
5
|
+
class BandsPlot:
|
6
|
+
_UNITS = "meV"
|
7
|
+
_NSYSTEMS = 1
|
8
|
+
|
9
|
+
def __init__(self, bands, kpath, **kwargs):
|
10
|
+
self.bands = bands
|
11
|
+
self.kpath = kpath
|
12
|
+
self.bands *= 1000
|
13
|
+
|
14
|
+
plot_options = kwargs
|
15
|
+
self.linewidth = plot_options.pop("linewidth", 1.5)
|
16
|
+
self.color = plot_options.pop("color", "blue")
|
17
|
+
self.fontsize = plot_options.pop("fontsize", 12)
|
18
|
+
self.ticksize = plot_options.pop("ticksize", 10)
|
19
|
+
self.plot_options = plot_options
|
20
|
+
|
21
|
+
def plot(self, filename=None):
|
22
|
+
fig, axs = plt.subplots(1, self._NSYSTEMS, constrained_layout=True)
|
23
|
+
|
24
|
+
kdata = np.arange(self.bands.shape[0])
|
25
|
+
for band in self.bands.T:
|
26
|
+
axs.plot(
|
27
|
+
kdata,
|
28
|
+
band,
|
29
|
+
linewidth=self.linewidth,
|
30
|
+
color=self.color,
|
31
|
+
**self.plot_options,
|
32
|
+
)
|
33
|
+
|
34
|
+
bmin, bmax = self.bands.min(), self.bands.max()
|
35
|
+
ymin, ymax = (
|
36
|
+
bmin - 0.05 * np.abs(bmin - bmax),
|
37
|
+
bmax + 0.05 * np.abs(bmax - bmin),
|
38
|
+
)
|
39
|
+
|
40
|
+
axs.set_ylim([ymin, ymax])
|
41
|
+
axs.set_xlim([0, kdata[-1]])
|
42
|
+
|
43
|
+
kpoint_labels = list(zip(*self.kpath))
|
44
|
+
axs.set_xticks(*kpoint_labels, fontsize=self.ticksize)
|
45
|
+
axs.vlines(
|
46
|
+
x=kpoint_labels[0],
|
47
|
+
ymin=ymin,
|
48
|
+
ymax=ymax,
|
49
|
+
color="black",
|
50
|
+
linewidth=self.linewidth / 5,
|
51
|
+
)
|
52
|
+
|
53
|
+
axs.set_ylabel(f"Energy ({self._UNITS})", fontsize=self.fontsize)
|
54
|
+
|
55
|
+
if filename is None:
|
56
|
+
plt.show()
|
57
|
+
else:
|
58
|
+
fig.save(filename, dpi=300, bbox_inches="tight")
|