TB2J 0.9.9.9__py3-none-any.whl → 0.9.9.12__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/Jtensor.py +5 -6
- TB2J/MAEGreen.py +8 -0
- TB2J/__init__.py +1 -1
- TB2J/exchange.py +1 -1
- TB2J/interfaces/abacus/stru_api.py +3 -1
- TB2J/interfaces/siesta_interface.py +2 -1
- TB2J/io_exchange/io_exchange.py +65 -23
- TB2J/io_exchange/io_vampire.py +6 -3
- TB2J/io_merge.py +15 -1
- TB2J/magnon/__init__.py +2 -2
- TB2J/magnon/magnon3.py +566 -56
- TB2J/magnon/magnon_band.py +185 -0
- TB2J/magnon/magnon_math.py +1 -0
- TB2J/magnon/plot.py +60 -21
- TB2J/mathutils/auto_kpath.py +151 -0
- tb2j-0.9.9.12.data/scripts/TB2J_plot_magnon_bands.py +7 -0
- {tb2j-0.9.9.9.dist-info → tb2j-0.9.9.12.dist-info}/METADATA +4 -2
- {tb2j-0.9.9.9.dist-info → tb2j-0.9.9.12.dist-info}/RECORD +33 -30
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_downfold.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_eigen.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_magnon.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_magnon2.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_magnon_dos.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_merge.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_rotate.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/TB2J_rotateDM.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/abacus2J.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/siesta2J.py +0 -0
- {tb2j-0.9.9.9.data → tb2j-0.9.9.12.data}/scripts/wann2J.py +0 -0
- {tb2j-0.9.9.9.dist-info → tb2j-0.9.9.12.dist-info}/WHEEL +0 -0
- {tb2j-0.9.9.9.dist-info → tb2j-0.9.9.12.dist-info}/entry_points.txt +0 -0
- {tb2j-0.9.9.9.dist-info → tb2j-0.9.9.12.dist-info}/licenses/LICENSE +0 -0
- {tb2j-0.9.9.9.dist-info → tb2j-0.9.9.12.dist-info}/top_level.txt +0 -0
TB2J/magnon/magnon3.py
CHANGED
@@ -1,11 +1,58 @@
|
|
1
|
-
from dataclasses import dataclass
|
1
|
+
from dataclasses import asdict, dataclass
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import List, Optional, Tuple, Union
|
2
4
|
|
3
5
|
import numpy as np
|
6
|
+
import tomli
|
7
|
+
import tomli_w
|
4
8
|
from scipy.spatial.transform import Rotation
|
5
9
|
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from .
|
10
|
+
from TB2J.io_exchange import SpinIO
|
11
|
+
from TB2J.magnon.magnon_band import MagnonBand
|
12
|
+
from TB2J.magnon.magnon_math import get_rotation_arrays
|
13
|
+
from TB2J.mathutils.auto_kpath import auto_kpath
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class MagnonParameters:
|
18
|
+
"""Parameters for magnon band structure calculations"""
|
19
|
+
|
20
|
+
path: str = "TB2J_results"
|
21
|
+
kpath: str = None
|
22
|
+
npoints: int = 300
|
23
|
+
filename: str = "magnon_bands.png"
|
24
|
+
Jiso: bool = True
|
25
|
+
Jani: bool = False
|
26
|
+
DMI: bool = False
|
27
|
+
Q: Optional[List[float]] = None
|
28
|
+
uz_file: Optional[str] = None
|
29
|
+
n: Optional[List[float]] = None
|
30
|
+
show: bool = False
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def from_toml(cls, filename: str) -> "MagnonParameters":
|
34
|
+
"""Load parameters from a TOML file"""
|
35
|
+
with open(filename, "rb") as f:
|
36
|
+
data = tomli.load(f)
|
37
|
+
return cls(**data)
|
38
|
+
|
39
|
+
def to_toml(self, filename: str):
|
40
|
+
"""Save parameters to a TOML file"""
|
41
|
+
# Convert to dict and remove None values
|
42
|
+
data = {k: v for k, v in asdict(self).items() if v is not None}
|
43
|
+
with open(filename, "wb") as f:
|
44
|
+
tomli_w.dump(data, f)
|
45
|
+
|
46
|
+
def __post_init__(self):
|
47
|
+
"""Validate parameters after initialization"""
|
48
|
+
if self.Q is not None and len(self.Q) != 3:
|
49
|
+
raise ValueError("Q must be a list of 3 numbers")
|
50
|
+
if self.n is not None and len(self.n) != 3:
|
51
|
+
raise ValueError("n must be a list of 3 numbers")
|
52
|
+
|
53
|
+
# Convert path to absolute path if uz_file is relative to it
|
54
|
+
if self.uz_file and not Path(self.uz_file).is_absolute():
|
55
|
+
self.uz_file = str(Path(self.path) / self.uz_file)
|
9
56
|
|
10
57
|
|
11
58
|
@dataclass
|
@@ -19,9 +66,11 @@ class Magnon:
|
|
19
66
|
magmom: np.ndarray
|
20
67
|
Rlist: np.ndarray
|
21
68
|
JR: np.ndarray
|
22
|
-
|
23
|
-
|
24
|
-
|
69
|
+
cell: np.ndarray
|
70
|
+
_Q: np.ndarray
|
71
|
+
_uz: np.ndarray
|
72
|
+
_n: np.ndarray
|
73
|
+
pbc: tuple = (True, True, True)
|
25
74
|
|
26
75
|
def set_reference(self, Q, uz, n):
|
27
76
|
"""
|
@@ -102,7 +151,7 @@ class Magnon:
|
|
102
151
|
# J'_mn(R) = R_m(ϕ)^T J(R) R_n(ϕ) using Einstein summation.
|
103
152
|
# Here m is always in the R=0, thus the rotation is only applied on the
|
104
153
|
# n , so only on the right.
|
105
|
-
JRprime[iR] = np.einsum("
|
154
|
+
JRprime[iR] = np.einsum(" ijxy, yb -> ijxb", JR[iR], Rmat)
|
106
155
|
|
107
156
|
nkpt = kpoints.shape[0]
|
108
157
|
Jq = np.zeros((nkpt, self.nspin, self.nspin, 3, 3), dtype=complex)
|
@@ -113,15 +162,13 @@ class Magnon:
|
|
113
162
|
phase = 2 * np.pi * R @ qpt
|
114
163
|
Jq[iqpt] += np.exp(1j * phase) * JRprime[iR]
|
115
164
|
|
116
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
)
|
121
|
-
Jq[iqpt, :, :, :, :] /= 2.0
|
165
|
+
#Jq_copy = Jq.copy()
|
166
|
+
#Jq.swapaxes(-1, -2) # swap xyz
|
167
|
+
#Jq.swapaxes(-3, -4) # swap ij
|
168
|
+
#Jq = (Jq.conj() + Jq_copy) / 2.0
|
122
169
|
return Jq
|
123
170
|
|
124
|
-
def Hq(self, kpoints
|
171
|
+
def Hq(self, kpoints):
|
125
172
|
"""
|
126
173
|
Compute the magnon Hamiltonian in reciprocal space.
|
127
174
|
|
@@ -140,22 +187,28 @@ class Magnon:
|
|
140
187
|
magmoms = self.magmom.copy()
|
141
188
|
magmoms /= np.linalg.norm(magmoms, axis=-1)[:, None]
|
142
189
|
|
190
|
+
|
143
191
|
U, V = get_rotation_arrays(magmoms, u=self._uz)
|
144
192
|
|
145
|
-
J0 = self.Jq(np.zeros((1, 3))
|
146
|
-
J0 = -Hermitize(J0)[:, :, 0]
|
147
|
-
Jq = -Hermitize(self.Jq(kpoints, anisotropic=anisotropic))
|
193
|
+
J0 = -self.Jq(np.zeros((1, 3)))[0]
|
194
|
+
# J0 = -Hermitize(J0)[:, :, 0]
|
195
|
+
# Jq = -Hermitize(self.Jq(kpoints, anisotropic=anisotropic))
|
196
|
+
|
197
|
+
Jq = -self.Jq(kpoints)
|
198
|
+
print(f"J0 shape: {J0.shape}")
|
148
199
|
|
149
200
|
C = np.diag(np.einsum("ix,ijxy,jy->i", V, 2 * J0, V))
|
150
|
-
B = np.einsum("ix,
|
151
|
-
A1 = np.einsum("ix,
|
152
|
-
A2 = np.einsum("ix,
|
201
|
+
B = np.einsum("ix,kijxy,jy->kij", U, Jq, U)
|
202
|
+
A1 = np.einsum("ix,kijxy,jy->kij", U, Jq, U.conj())
|
203
|
+
A2 = np.einsum("ix,kijxy,jy->kij", U.conj(), Jq, U)
|
153
204
|
|
154
|
-
|
205
|
+
H = np.block([[A1 - C, B], [B.swapaxes(-1, -2).conj(), A2 - C]])
|
206
|
+
print(f"H shape: {H.shape}")
|
207
|
+
return H
|
155
208
|
|
156
|
-
def _magnon_energies(self, kpoints,
|
209
|
+
def _magnon_energies(self, kpoints, u=None):
|
157
210
|
"""Calculate magnon energies"""
|
158
|
-
H = self.Hq(kpoints
|
211
|
+
H = self.Hq(kpoints)
|
159
212
|
n = H.shape[-1] // 2
|
160
213
|
I = np.eye(n)
|
161
214
|
|
@@ -176,8 +229,9 @@ class Magnon:
|
|
176
229
|
|
177
230
|
g = np.block([[1 * I, 0 * I], [0 * I, -1 * I]])
|
178
231
|
KH = K.swapaxes(-1, -2).conj()
|
179
|
-
|
232
|
+
# Why only n:?
|
180
233
|
return np.linalg.eigvalsh(KH @ g @ K)[:, n:] + min_eig
|
234
|
+
# return np.linalg.eigvalsh(KH @ g @ K)[:, :] + min_eig
|
181
235
|
|
182
236
|
def get_magnon_bands(
|
183
237
|
self,
|
@@ -192,33 +246,84 @@ class Magnon:
|
|
192
246
|
anisotropic: bool = True,
|
193
247
|
u: np.array = None,
|
194
248
|
):
|
195
|
-
"""Get magnon band structure
|
196
|
-
|
249
|
+
"""Get magnon band structure.
|
250
|
+
|
251
|
+
Parameters
|
252
|
+
----------
|
253
|
+
kpoints : np.array, optional
|
254
|
+
Explicit k-points to calculate bands at. If empty, generates k-points from path.
|
255
|
+
path : str, optional
|
256
|
+
String specifying the k-path. If None, generates automatically using auto_kpath.
|
257
|
+
npoints : int, optional
|
258
|
+
Number of k-points along the path. Default is 300.
|
259
|
+
special_points : dict, optional
|
260
|
+
Dictionary of special points coordinates.
|
261
|
+
tol : float, optional
|
262
|
+
Tolerance for k-point comparisons. Default is 2e-4.
|
263
|
+
pbc : tuple, optional
|
264
|
+
Periodic boundary conditions. Default is None.
|
265
|
+
cartesian : bool, optional
|
266
|
+
Whether k-points are in cartesian coordinates. Default is False.
|
267
|
+
labels : list, optional
|
268
|
+
List of k-point labels. Default is None.
|
269
|
+
anisotropic : bool, optional
|
270
|
+
Whether to include anisotropic interactions. Default is True.
|
271
|
+
u : np.array, optional
|
272
|
+
Quantization axis. Default is None.
|
273
|
+
|
274
|
+
Returns
|
275
|
+
-------
|
276
|
+
tuple
|
277
|
+
- labels : list of (index, name) tuples for special k-points
|
278
|
+
- bands : array of band energies
|
279
|
+
- xlist : list of arrays with x-coordinates for plotting (if using auto_kpath)
|
280
|
+
"""
|
281
|
+
pbc = self.pbc if pbc is None else pbc
|
282
|
+
pbc = [True, True, True]
|
197
283
|
u = self._uz if u is None else u
|
198
284
|
if kpoints.size == 0:
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
285
|
+
if path is None:
|
286
|
+
# Use auto_kpath to generate path automatically
|
287
|
+
xlist, kptlist, Xs, knames, spk = auto_kpath(
|
288
|
+
self.cell, None, npoints=npoints
|
289
|
+
)
|
290
|
+
kpoints = np.concatenate(kptlist)
|
291
|
+
# Create labels from special points
|
292
|
+
labels = []
|
293
|
+
current_pos = 0
|
294
|
+
for i, (x, k) in enumerate(zip(xlist, kptlist)):
|
295
|
+
for name in knames:
|
296
|
+
matches = np.where((k == spk[name]).all(axis=1))[0]
|
297
|
+
if matches.size > 0:
|
298
|
+
labels.append((matches[0] + current_pos, name))
|
299
|
+
current_pos += len(k)
|
300
|
+
else:
|
301
|
+
bandpath = self.cell.bandpath(
|
302
|
+
path=path,
|
303
|
+
npoints=npoints,
|
304
|
+
special_points=special_points,
|
305
|
+
eps=tol,
|
306
|
+
pbc=pbc,
|
307
|
+
)
|
308
|
+
kpoints = bandpath.kpts
|
309
|
+
spk = bandpath.special_points
|
310
|
+
spk[r"$\Gamma$"] = spk.pop("G", np.zeros(3))
|
311
|
+
labels = [
|
312
|
+
(i, symbol)
|
313
|
+
for symbol in spk
|
314
|
+
for i in np.where((kpoints == spk[symbol]).all(axis=1))[0]
|
315
|
+
]
|
216
316
|
elif cartesian:
|
217
|
-
kpoints = np.linalg.solve(self.
|
317
|
+
kpoints = np.linalg.solve(self.cell.T, kpoints.T).T
|
218
318
|
|
219
|
-
bands = self._magnon_energies(kpoints
|
319
|
+
bands = self._magnon_energies(kpoints)
|
320
|
+
print(f"bands shape: {bands.shape}")
|
220
321
|
|
221
|
-
|
322
|
+
if path is None and kpoints.size == 0: # Fixed condition
|
323
|
+
# When using auto_kpath, return xlist for segmented plotting
|
324
|
+
return labels, bands, xlist
|
325
|
+
else:
|
326
|
+
return labels, bands, None
|
222
327
|
|
223
328
|
def plot_magnon_bands(self, **kwargs):
|
224
329
|
"""
|
@@ -227,12 +332,41 @@ class Magnon:
|
|
227
332
|
Parameters
|
228
333
|
----------
|
229
334
|
**kwargs
|
230
|
-
Additional keyword arguments passed to get_magnon_bands and plotting functions
|
335
|
+
Additional keyword arguments passed to get_magnon_bands and plotting functions.
|
336
|
+
Supported plotting options:
|
337
|
+
- filename : str, optional
|
338
|
+
Output filename for saving the plot
|
339
|
+
- ax : matplotlib.axes.Axes, optional
|
340
|
+
Axes for plotting. If None, creates new figure
|
341
|
+
- show : bool, optional
|
342
|
+
Whether to show the plot on screen
|
231
343
|
"""
|
232
344
|
filename = kwargs.pop("filename", None)
|
233
|
-
|
234
|
-
|
235
|
-
|
345
|
+
kpath_labels, bands, xlist = self.get_magnon_bands(**kwargs)
|
346
|
+
|
347
|
+
# Get k-points and special points
|
348
|
+
if "path" in kwargs and kwargs["path"] is None:
|
349
|
+
_, kptlist, _, _, spk = auto_kpath(
|
350
|
+
self.cell, None, npoints=kwargs.get("npoints", 300)
|
351
|
+
)
|
352
|
+
kpoints = np.concatenate(kptlist)
|
353
|
+
else:
|
354
|
+
bandpath = self.cell.bandpath(
|
355
|
+
path=kwargs.get("path", "GXMG"), npoints=kwargs.get("npoints", 300)
|
356
|
+
)
|
357
|
+
kpoints = bandpath.kpts
|
358
|
+
spk = bandpath.special_points.copy()
|
359
|
+
spk[r"$\Gamma$"] = spk.pop("G", np.zeros(3))
|
360
|
+
|
361
|
+
bands_plot = MagnonBand(
|
362
|
+
energies=bands * 1000, # Convert to meV
|
363
|
+
kpoints=kpoints,
|
364
|
+
kpath_labels=kpath_labels,
|
365
|
+
special_points=spk,
|
366
|
+
xcoords=xlist,
|
367
|
+
)
|
368
|
+
|
369
|
+
return bands_plot.plot(filename=filename, **kwargs)
|
236
370
|
|
237
371
|
@classmethod
|
238
372
|
def load_from_io(cls, exc: SpinIO, **kwargs):
|
@@ -251,11 +385,23 @@ class Magnon:
|
|
251
385
|
Magnon
|
252
386
|
Initialized Magnon instance
|
253
387
|
"""
|
388
|
+
# Get magnetic moments for magnetic atoms
|
389
|
+
magmoms = exc.get_magnetic_moments()
|
390
|
+
# nspin = len(magmoms) # Number of magnetic atoms
|
391
|
+
|
392
|
+
cell = exc.atoms.get_cell()
|
393
|
+
pbc = exc.atoms.get_pbc()
|
394
|
+
|
254
395
|
return cls(
|
255
396
|
nspin=exc.nspin,
|
256
|
-
magmom=
|
397
|
+
magmom=magmoms,
|
257
398
|
Rlist=exc.Rlist,
|
258
|
-
JR=exc.get_full_Jtensor_for_Rlist(order="ij33", **kwargs),
|
399
|
+
JR=exc.get_full_Jtensor_for_Rlist(order="ij33", asr=False, **kwargs),
|
400
|
+
cell=cell,
|
401
|
+
_Q=np.zeros(3), # Default propagation vector
|
402
|
+
_uz=np.array([[0.0, 0.0, 1.0]]), # Default quantization axis
|
403
|
+
_n=np.array([0.0, 0.0, 1.0]), # Default rotation axis
|
404
|
+
pbc=pbc,
|
259
405
|
)
|
260
406
|
|
261
407
|
@classmethod
|
@@ -294,7 +440,7 @@ def test_magnon(path="TB2J_results"):
|
|
294
440
|
|
295
441
|
# Load magnon calculator from TB2J results
|
296
442
|
print(f"Loading exchange parameters from {path}...")
|
297
|
-
magnon = Magnon.from_TB2J_results(path=path,
|
443
|
+
magnon = Magnon.from_TB2J_results(path=path, Jiso=True, Jani=False, DMI=False)
|
298
444
|
|
299
445
|
# Define high-symmetry points for a cube
|
300
446
|
kpoints = np.array(
|
@@ -325,10 +471,374 @@ def test_magnon(path="TB2J_results"):
|
|
325
471
|
print("-" * 50)
|
326
472
|
for i, (k, label) in enumerate(zip(kpoints, klabels)):
|
327
473
|
print(f"\n{label}-point k={k}:")
|
328
|
-
print(f"Energies: {energies[i] * 1000:.3f} meV") # Convert to meV
|
474
|
+
# print(f"Energies: {energies[i] * 1000:.3f} meV") # Convert to meV
|
475
|
+
print(f"Energies: {energies[i] * 1000} meV") # Convert to meV
|
476
|
+
|
477
|
+
print("\nPlotting magnon bands...")
|
478
|
+
magnon.plot_magnon_bands(
|
479
|
+
# kpoints=kpoints,
|
480
|
+
# labels=klabels,
|
481
|
+
path="GHPGPH,PN",
|
482
|
+
filename="magnon_bands.png",
|
483
|
+
)
|
329
484
|
|
330
485
|
return magnon, Jq, energies
|
331
486
|
|
332
487
|
|
488
|
+
def create_plot_script(filename: str):
|
489
|
+
"""Create a Python script for plotting the saved band structure data.
|
490
|
+
|
491
|
+
Parameters
|
492
|
+
----------
|
493
|
+
filename : str
|
494
|
+
Base filename (without extension) to use for the plot script
|
495
|
+
"""
|
496
|
+
script_name = f"plot_{filename}.py"
|
497
|
+
script = '''#!/usr/bin/env python3
|
498
|
+
"""Simple script to plot magnon band structure from saved data."""
|
499
|
+
|
500
|
+
from TB2J.magnon.magnon_band import MagnonBand
|
501
|
+
import matplotlib.pyplot as plt
|
502
|
+
|
503
|
+
def plot_magnon_bands(input_file, output_file=None, ax=None, color='blue', show=True):
|
504
|
+
"""Load and plot magnon band structure.
|
505
|
+
|
506
|
+
Parameters
|
507
|
+
----------
|
508
|
+
input_file : str
|
509
|
+
JSON file containing band structure data
|
510
|
+
output_file : str, optional
|
511
|
+
Output file for saving the plot
|
512
|
+
ax : matplotlib.axes.Axes, optional
|
513
|
+
Axes for plotting. If None, creates new figure
|
514
|
+
color : str, optional
|
515
|
+
Color of the band lines (default: blue)
|
516
|
+
show : bool, optional
|
517
|
+
Whether to show the plot on screen (default: True)
|
518
|
+
|
519
|
+
Returns
|
520
|
+
-------
|
521
|
+
matplotlib.axes.Axes
|
522
|
+
The plotting axes
|
523
|
+
"""
|
524
|
+
# Load band structure data
|
525
|
+
bands = MagnonBand.load(input_file)
|
526
|
+
|
527
|
+
# Create plot
|
528
|
+
ax = bands.plot(
|
529
|
+
ax=ax,
|
530
|
+
filename=output_file,
|
531
|
+
color=color,
|
532
|
+
show=show
|
533
|
+
)
|
534
|
+
return ax
|
535
|
+
|
536
|
+
if __name__ == "__main__":
|
537
|
+
# Usage example
|
538
|
+
# Example usage
|
539
|
+
import matplotlib.pyplot as plt
|
540
|
+
|
541
|
+
# Create a figure and axis (optional)
|
542
|
+
fig, ax = plt.subplots(figsize=(6, 4))
|
543
|
+
|
544
|
+
# Plot bands with custom color on given axis
|
545
|
+
plot_magnon_bands(
|
546
|
+
input_file="magnon_bands.json",
|
547
|
+
output_file="magnon_bands.png",
|
548
|
+
ax=ax,
|
549
|
+
color='red',
|
550
|
+
show=True
|
551
|
+
)
|
552
|
+
'''
|
553
|
+
|
554
|
+
with open(script_name, "w") as f:
|
555
|
+
f.write(script)
|
556
|
+
|
557
|
+
import os
|
558
|
+
|
559
|
+
os.chmod(script_name, 0o755) # Make executable
|
560
|
+
|
561
|
+
|
562
|
+
def save_bands_data(
|
563
|
+
kpoints: np.ndarray,
|
564
|
+
energies: np.ndarray,
|
565
|
+
kpath_labels: List[Tuple[int, str]],
|
566
|
+
special_points: dict,
|
567
|
+
xcoords: Optional[Union[np.ndarray, List[np.ndarray]]],
|
568
|
+
filename: str,
|
569
|
+
):
|
570
|
+
"""Save magnon band structure data to a JSON file using MagnonBand class.
|
571
|
+
|
572
|
+
Parameters
|
573
|
+
----------
|
574
|
+
kpoints : array_like
|
575
|
+
Array of k-points coordinates
|
576
|
+
energies : array_like
|
577
|
+
Array of band energies (in meV)
|
578
|
+
kpath_labels : list of (int, str)
|
579
|
+
List of tuples containing k-point indices and their labels
|
580
|
+
special_points : dict
|
581
|
+
Dictionary of special points and their coordinates
|
582
|
+
xcoords : array_like or list of arrays
|
583
|
+
x-coordinates for plotting (can be segmented)
|
584
|
+
filename : str
|
585
|
+
Output filename
|
586
|
+
"""
|
587
|
+
from TB2J.magnon.magnon_band import MagnonBand # Using same import as above
|
588
|
+
|
589
|
+
bands = MagnonBand(
|
590
|
+
energies=energies,
|
591
|
+
kpoints=kpoints,
|
592
|
+
kpath_labels=kpath_labels,
|
593
|
+
special_points=special_points,
|
594
|
+
xcoords=xcoords,
|
595
|
+
)
|
596
|
+
bands.save(filename)
|
597
|
+
|
598
|
+
# Create plotting script
|
599
|
+
base_name = filename.rsplit(".", 1)[0]
|
600
|
+
create_plot_script(base_name)
|
601
|
+
|
602
|
+
print(f"Band structure data saved to {filename}")
|
603
|
+
print(f"Created plotting script: plot_{base_name}.py")
|
604
|
+
print("Usage: ")
|
605
|
+
print(f"See plot_{base_name}.py for example usage")
|
606
|
+
|
607
|
+
return bands
|
608
|
+
|
609
|
+
|
610
|
+
def plot_magnon_bands_from_TB2J(
|
611
|
+
params: MagnonParameters,
|
612
|
+
):
|
613
|
+
"""
|
614
|
+
Load TB2J results and plot magnon band structure along a specified k-path.
|
615
|
+
|
616
|
+
Parameters
|
617
|
+
----------
|
618
|
+
path : str, optional
|
619
|
+
Path to TB2J results directory, default is "TB2J_results"
|
620
|
+
kpath : str, optional
|
621
|
+
String specifying the k-path, e.g. "GXMR" for Gamma-X-M-R path
|
622
|
+
Default is "GXMR"
|
623
|
+
npoints : int, optional
|
624
|
+
Number of k-points along the path, default is 300
|
625
|
+
filename : str, optional
|
626
|
+
Output file name for the plot, default is "magnon_bands.png"
|
627
|
+
Jiso : bool, optional
|
628
|
+
Include isotropic exchange interactions, default is True
|
629
|
+
Jani : bool, optional
|
630
|
+
Include anisotropic exchange interactions, default is False
|
631
|
+
DMI : bool, optional
|
632
|
+
Include Dzyaloshinskii-Moriya interactions, default is False
|
633
|
+
Q : array-like, optional
|
634
|
+
Propagation vector [Qx, Qy, Qz], default is [0, 0, 0]
|
635
|
+
uz_file : str, optional
|
636
|
+
Path to file containing quantization axes for each spin (natom×3 array)
|
637
|
+
If not provided, default [0, 0, 1] will be used for all spins
|
638
|
+
n : array-like, optional
|
639
|
+
Normal vector for rotation [nx, ny, nz], default is [0, 0, 1]
|
640
|
+
show: bool, optional
|
641
|
+
whether to show figure.
|
642
|
+
|
643
|
+
Returns
|
644
|
+
-------
|
645
|
+
magnon : Magnon
|
646
|
+
The Magnon instance used for calculations
|
647
|
+
"""
|
648
|
+
# Load magnon calculator from TB2J results
|
649
|
+
print(f"Loading exchange parameters from {params.path}...")
|
650
|
+
magnon = Magnon.from_TB2J_results(
|
651
|
+
path=params.path, Jiso=params.Jiso, Jani=params.Jani, DMI=params.DMI
|
652
|
+
)
|
653
|
+
|
654
|
+
# Set reference vectors if provided
|
655
|
+
if any(x is not None for x in [params.Q, params.uz_file, params.n]):
|
656
|
+
Q = [0, 0, 0] if params.Q is None else params.Q
|
657
|
+
n = [0, 0, 1] if params.n is None else params.n
|
658
|
+
|
659
|
+
# Handle quantization axes
|
660
|
+
if params.uz_file is not None:
|
661
|
+
uz = np.loadtxt(params.uz_file)
|
662
|
+
if uz.shape[1] != 3:
|
663
|
+
raise ValueError(
|
664
|
+
f"Quantization axes file should contain a natom×3 array. Got shape {uz.shape}"
|
665
|
+
)
|
666
|
+
if uz.shape[0] != magnon.nspin:
|
667
|
+
raise ValueError(
|
668
|
+
f"Number of spins in uz file ({uz.shape[0]}) does not match the system ({magnon.nspin})"
|
669
|
+
)
|
670
|
+
else:
|
671
|
+
# Default: [0, 0, 1] for all spins
|
672
|
+
uz = np.array([[0.0, 0.0, 1.0] for _ in range(magnon.nspin)])
|
673
|
+
|
674
|
+
magnon.set_reference(Q, uz, n)
|
675
|
+
|
676
|
+
# Get band structure data
|
677
|
+
print(f"\nCalculating bands along path {params.kpath}...")
|
678
|
+
kpath_labels, bands, xlist = magnon.get_magnon_bands(
|
679
|
+
path=params.kpath,
|
680
|
+
npoints=params.npoints,
|
681
|
+
)
|
682
|
+
|
683
|
+
# Convert energies to meV
|
684
|
+
bands_meV = bands * 1000
|
685
|
+
|
686
|
+
# Save band structure data and create plot
|
687
|
+
data_file = params.filename.rsplit(".", 1)[0] + ".json"
|
688
|
+
print(f"\nSaving band structure data to {data_file}")
|
689
|
+
|
690
|
+
# Get k-points and special points
|
691
|
+
if params.kpath is None:
|
692
|
+
_, kptlist, _, _, spk = auto_kpath(magnon.cell, None, npoints=params.npoints)
|
693
|
+
kpoints = np.concatenate(kptlist)
|
694
|
+
else:
|
695
|
+
bandpath = magnon.cell.bandpath(path=params.kpath, npoints=params.npoints)
|
696
|
+
kpoints = bandpath.kpts
|
697
|
+
spk = bandpath.special_points
|
698
|
+
spk[r"$\Gamma$"] = spk.pop("G", np.zeros(3))
|
699
|
+
|
700
|
+
magnon_bands = save_bands_data(
|
701
|
+
kpoints=kpoints,
|
702
|
+
energies=bands_meV,
|
703
|
+
kpath_labels=kpath_labels,
|
704
|
+
special_points=spk,
|
705
|
+
xcoords=xlist,
|
706
|
+
filename=data_file,
|
707
|
+
)
|
708
|
+
|
709
|
+
# Plot band structure
|
710
|
+
print(f"Plotting bands to {params.filename}")
|
711
|
+
magnon_bands.plot(filename=params.filename)
|
712
|
+
|
713
|
+
return magnon
|
714
|
+
|
715
|
+
|
716
|
+
def main():
|
717
|
+
"""Command line interface for plotting magnon bands from TB2J results."""
|
718
|
+
import argparse
|
719
|
+
|
720
|
+
parser = argparse.ArgumentParser(
|
721
|
+
description="Plot magnon band structure from TB2J results"
|
722
|
+
)
|
723
|
+
|
724
|
+
# Add a mutually exclusive group for config file vs. command line arguments
|
725
|
+
group = parser.add_mutually_exclusive_group()
|
726
|
+
group.add_argument(
|
727
|
+
"--config",
|
728
|
+
type=str,
|
729
|
+
help="Path to TOML configuration file",
|
730
|
+
)
|
731
|
+
group.add_argument(
|
732
|
+
"--save-config",
|
733
|
+
type=str,
|
734
|
+
help="Save default configuration to specified TOML file",
|
735
|
+
)
|
736
|
+
|
737
|
+
# Command line arguments (used if no config file is provided)
|
738
|
+
parser.add_argument(
|
739
|
+
"--path",
|
740
|
+
default="TB2J_results",
|
741
|
+
help="Path to TB2J results directory (default: TB2J_results)",
|
742
|
+
)
|
743
|
+
parser.add_argument(
|
744
|
+
"--kpath",
|
745
|
+
default=None,
|
746
|
+
help="k-path specification (default: auto-detected from type of cell)",
|
747
|
+
)
|
748
|
+
parser.add_argument(
|
749
|
+
"--npoints",
|
750
|
+
type=int,
|
751
|
+
default=300,
|
752
|
+
help="Number of k-points along the path (default: 300)",
|
753
|
+
)
|
754
|
+
parser.add_argument(
|
755
|
+
"--output",
|
756
|
+
default="magnon_bands.png",
|
757
|
+
help="Output file name (default: magnon_bands.png)",
|
758
|
+
)
|
759
|
+
parser.add_argument(
|
760
|
+
"--Jiso",
|
761
|
+
action="store_true",
|
762
|
+
default=True,
|
763
|
+
help="Include isotropic exchange interactions (default: True)",
|
764
|
+
)
|
765
|
+
parser.add_argument(
|
766
|
+
"--no-Jiso",
|
767
|
+
action="store_false",
|
768
|
+
dest="Jiso",
|
769
|
+
help="Exclude isotropic exchange interactions",
|
770
|
+
)
|
771
|
+
parser.add_argument(
|
772
|
+
"--Jani",
|
773
|
+
action="store_true",
|
774
|
+
default=False,
|
775
|
+
help="Include anisotropic exchange interactions (default: False)",
|
776
|
+
)
|
777
|
+
parser.add_argument(
|
778
|
+
"--DMI",
|
779
|
+
action="store_true",
|
780
|
+
default=False,
|
781
|
+
help="Include Dzyaloshinskii-Moriya interactions (default: False)",
|
782
|
+
)
|
783
|
+
parser.add_argument(
|
784
|
+
"--Q",
|
785
|
+
nargs=3,
|
786
|
+
type=float,
|
787
|
+
metavar=("Qx", "Qy", "Qz"),
|
788
|
+
help="Propagation vector [Qx, Qy, Qz] (default: [0, 0, 0])",
|
789
|
+
)
|
790
|
+
parser.add_argument(
|
791
|
+
"--uz-file",
|
792
|
+
type=str,
|
793
|
+
help="Path to file containing quantization axes for each spin (natom×3 array)",
|
794
|
+
)
|
795
|
+
parser.add_argument(
|
796
|
+
"--n",
|
797
|
+
nargs=3,
|
798
|
+
type=float,
|
799
|
+
metavar=("nx", "ny", "nz"),
|
800
|
+
help="Normal vector for rotation [nx, ny, nz] (default: [0, 0, 1])",
|
801
|
+
)
|
802
|
+
|
803
|
+
parser.add_argument(
|
804
|
+
"--show",
|
805
|
+
action="store_true",
|
806
|
+
default=False,
|
807
|
+
help="show figure on screen.",
|
808
|
+
)
|
809
|
+
|
810
|
+
|
811
|
+
args = parser.parse_args()
|
812
|
+
|
813
|
+
# Handle configuration file options
|
814
|
+
if args.save_config:
|
815
|
+
# Create default parameters and save to file
|
816
|
+
params = MagnonParameters()
|
817
|
+
params.to_toml(args.save_config)
|
818
|
+
print(f"Saved default configuration to {args.save_config}")
|
819
|
+
return
|
820
|
+
|
821
|
+
if args.config:
|
822
|
+
# Load parameters from config file
|
823
|
+
params = MagnonParameters.from_toml(args.config)
|
824
|
+
else:
|
825
|
+
# Create parameters from command line arguments
|
826
|
+
params = MagnonParameters(
|
827
|
+
path=args.path,
|
828
|
+
kpath=args.kpath,
|
829
|
+
npoints=args.npoints,
|
830
|
+
filename=args.output,
|
831
|
+
Jiso=args.Jiso,
|
832
|
+
Jani=args.Jani,
|
833
|
+
DMI=args.DMI,
|
834
|
+
Q=args.Q if args.Q is not None else None,
|
835
|
+
uz_file=args.uz_file,
|
836
|
+
n=args.n if args.n is not None else None,
|
837
|
+
show=args.show
|
838
|
+
)
|
839
|
+
|
840
|
+
plot_magnon_bands_from_TB2J(params)
|
841
|
+
|
842
|
+
|
333
843
|
if __name__ == "__main__":
|
334
|
-
|
844
|
+
main()
|