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