TB2J 0.9.9rc18__py3-none-any.whl → 0.9.9.1__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/MAEGreen.py +11 -18
- TB2J/exchange_params.py +1 -6
- TB2J/interfaces/siesta_interface.py +7 -14
- TB2J/io_exchange/__init__.py +0 -2
- TB2J/io_exchange/io_exchange.py +5 -2
- TB2J/mathutils/__init__.py +0 -1
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/abacus2J.py +2 -6
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/siesta2J.py +1 -5
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/wann2J.py +2 -6
- {tb2j-0.9.9rc18.dist-info → tb2j-0.9.9.1.dist-info}/METADATA +3 -2
- {tb2j-0.9.9rc18.dist-info → tb2j-0.9.9.1.dist-info}/RECORD +22 -28
- TB2J/magnon/__init__.py +0 -3
- TB2J/magnon/io_exchange2.py +0 -688
- TB2J/magnon/plot.py +0 -58
- TB2J/magnon/structure.py +0 -348
- TB2J/mathutils/magnons.py +0 -45
- tb2j-0.9.9rc18.data/scripts/TB2J_magnon2.py +0 -77
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_downfold.py +0 -0
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_eigen.py +0 -0
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_magnon.py +0 -0
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_magnon_dos.py +0 -0
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_merge.py +0 -0
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_rotate.py +0 -0
- {tb2j-0.9.9rc18.data → tb2j-0.9.9.1.data}/scripts/TB2J_rotateDM.py +0 -0
- {tb2j-0.9.9rc18.dist-info → tb2j-0.9.9.1.dist-info}/WHEEL +0 -0
- {tb2j-0.9.9rc18.dist-info → tb2j-0.9.9.1.dist-info}/entry_points.txt +0 -0
- {tb2j-0.9.9rc18.dist-info → tb2j-0.9.9.1.dist-info}/licenses/LICENSE +0 -0
- {tb2j-0.9.9rc18.dist-info → tb2j-0.9.9.1.dist-info}/top_level.txt +0 -0
TB2J/magnon/io_exchange2.py
DELETED
@@ -1,688 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module provides functionality for handling magnetic exchange interactions and computing magnon band structures.
|
3
|
-
|
4
|
-
It includes classes and functions for:
|
5
|
-
- Reading and writing exchange interaction data
|
6
|
-
- Computing exchange tensors and magnon energies
|
7
|
-
- Plotting magnon band structures
|
8
|
-
- Converting between different magnetic structure representations
|
9
|
-
"""
|
10
|
-
|
11
|
-
import numpy as np
|
12
|
-
from scipy.spatial.transform import Rotation
|
13
|
-
|
14
|
-
from ..mathutils import generate_grid, get_rotation_arrays, round_to_precision, uz
|
15
|
-
from .plot import BandsPlot
|
16
|
-
from .structure import BaseMagneticStructure, get_attribute_array
|
17
|
-
|
18
|
-
__all__ = [
|
19
|
-
"ExchangeIO",
|
20
|
-
"plot_tb2j_magnon_bands",
|
21
|
-
]
|
22
|
-
|
23
|
-
|
24
|
-
def branched_keys(tb2j_keys, npairs):
|
25
|
-
"""
|
26
|
-
Organize TB2J keys into branches based on magnetic site pairs.
|
27
|
-
|
28
|
-
Parameters
|
29
|
-
----------
|
30
|
-
tb2j_keys : list
|
31
|
-
List of TB2J dictionary keys containing interaction information
|
32
|
-
npairs : int
|
33
|
-
Number of magnetic site pairs
|
34
|
-
|
35
|
-
Returns
|
36
|
-
-------
|
37
|
-
list
|
38
|
-
List of branched keys organized by magnetic site pairs
|
39
|
-
"""
|
40
|
-
msites = int((2 * npairs) ** 0.5)
|
41
|
-
branch_size = len(tb2j_keys) // msites**2
|
42
|
-
new_keys = sorted(tb2j_keys, key=lambda x: -x[1] + x[2])[
|
43
|
-
(npairs - msites) * branch_size :
|
44
|
-
]
|
45
|
-
new_keys.sort(key=lambda x: x[1:])
|
46
|
-
bkeys = [
|
47
|
-
new_keys[i : i + branch_size] for i in range(0, len(new_keys), branch_size)
|
48
|
-
]
|
49
|
-
|
50
|
-
return [sorted(branch, key=lambda x: np.linalg.norm(x[0])) for branch in bkeys]
|
51
|
-
|
52
|
-
|
53
|
-
def correct_content(content, quadratic=False):
|
54
|
-
"""
|
55
|
-
Ensure content dictionary has all required entries with proper initialization.
|
56
|
-
|
57
|
-
Parameters
|
58
|
-
----------
|
59
|
-
content : dict
|
60
|
-
Dictionary containing exchange interaction data
|
61
|
-
quadratic : bool, optional
|
62
|
-
Whether to include biquadratic interactions, by default False
|
63
|
-
"""
|
64
|
-
n = max(content["index_spin"]) + 1
|
65
|
-
data_shape = {"exchange_Jdict": ()}
|
66
|
-
|
67
|
-
if not content["colinear"]:
|
68
|
-
data_shape |= {"Jani_dict": (3, 3), "dmi_ddict": (3,)}
|
69
|
-
if quadratic:
|
70
|
-
data_shape["biquadratic_Jdict"] = (2,)
|
71
|
-
|
72
|
-
for label, shape in data_shape.items():
|
73
|
-
content[label] |= {((0, 0, 0), i, i): np.zeros(shape) for i in range(n)}
|
74
|
-
|
75
|
-
|
76
|
-
def Hermitize(array):
|
77
|
-
"""
|
78
|
-
Convert an array into its Hermitian form by constructing a Hermitian matrix.
|
79
|
-
|
80
|
-
A Hermitian matrix H has the property that H = H†, where H† is the conjugate transpose.
|
81
|
-
This means H[i,j] = conj(H[j,i]) for all indices i,j. The function takes an input array
|
82
|
-
representing the upper triangular part of the matrix and constructs the full Hermitian
|
83
|
-
matrix by:
|
84
|
-
1. Placing the input values in the upper triangular part
|
85
|
-
2. Computing the conjugate transpose of these values for the lower triangular part
|
86
|
-
|
87
|
-
This is commonly used in quantum mechanics and magnetic systems where Hamiltonians
|
88
|
-
must be Hermitian to ensure real eigenvalues.
|
89
|
-
|
90
|
-
Parameters
|
91
|
-
----------
|
92
|
-
array : numpy.ndarray
|
93
|
-
Input array containing the upper triangular elements of the matrix.
|
94
|
-
Shape should be (n*(n+1)/2, ...) where n is the dimension of
|
95
|
-
the resulting square matrix.
|
96
|
-
|
97
|
-
Returns
|
98
|
-
-------
|
99
|
-
numpy.ndarray
|
100
|
-
Full Hermitian matrix with shape (n, n, ...), where n is computed
|
101
|
-
from the input array size. The result satisfies result[i,j] = conj(result[j,i])
|
102
|
-
for all indices i,j.
|
103
|
-
|
104
|
-
Example
|
105
|
-
-------
|
106
|
-
>>> arr = np.array([1+0j, 2+1j, 3+0j]) # Upper triangular elements for 2x2 matrix
|
107
|
-
>>> Hermitize(arr)
|
108
|
-
array([[1.+0.j, 2.+1.j],
|
109
|
-
[2.-1.j, 3.+0.j]])
|
110
|
-
"""
|
111
|
-
n = int((2 * array.shape[0]) ** 0.5)
|
112
|
-
result = np.zeros((n, n) + array.shape[1:], dtype=complex)
|
113
|
-
u_indices = np.triu_indices(n)
|
114
|
-
|
115
|
-
result[*u_indices] = array
|
116
|
-
result.swapaxes(0, 1)[*u_indices] = array.swapaxes(-1, -2).conj()
|
117
|
-
|
118
|
-
return result
|
119
|
-
|
120
|
-
|
121
|
-
class ExchangeIO(BaseMagneticStructure):
|
122
|
-
"""
|
123
|
-
Class for handling magnetic exchange interactions and computing magnon properties.
|
124
|
-
|
125
|
-
This class provides functionality for:
|
126
|
-
- Managing magnetic structure information
|
127
|
-
- Computing exchange tensors
|
128
|
-
- Calculating magnon band structures
|
129
|
-
- Reading TB2J format files
|
130
|
-
- Visualizing magnon bands
|
131
|
-
|
132
|
-
Parameters
|
133
|
-
----------
|
134
|
-
atoms : ase.Atoms, optional
|
135
|
-
ASE atoms object containing the structure
|
136
|
-
cell : array_like, optional
|
137
|
-
3x3 matrix defining the unit cell
|
138
|
-
elements : list, optional
|
139
|
-
List of chemical symbols for atoms
|
140
|
-
positions : array_like, optional
|
141
|
-
Atomic positions
|
142
|
-
magmoms : array_like, optional
|
143
|
-
Magnetic moments for each atom
|
144
|
-
pbc : tuple, optional
|
145
|
-
Periodic boundary conditions, default (True, True, True)
|
146
|
-
magnetic_elements : list, optional
|
147
|
-
List of magnetic elements in the structure
|
148
|
-
kmesh : list, optional
|
149
|
-
k-point mesh dimensions, default [1, 1, 1]
|
150
|
-
collinear : bool, optional
|
151
|
-
Whether the magnetic structure is collinear, default True
|
152
|
-
"""
|
153
|
-
|
154
|
-
def __init__(
|
155
|
-
self,
|
156
|
-
atoms=None,
|
157
|
-
cell=None,
|
158
|
-
elements=None,
|
159
|
-
positions=None,
|
160
|
-
magmoms=None,
|
161
|
-
pbc=(True, True, True),
|
162
|
-
magnetic_elements=[],
|
163
|
-
kmesh=[1, 1, 1],
|
164
|
-
collinear=True,
|
165
|
-
):
|
166
|
-
super().__init__(
|
167
|
-
atoms=atoms,
|
168
|
-
cell=cell,
|
169
|
-
elements=elements,
|
170
|
-
positions=positions,
|
171
|
-
pbc=pbc,
|
172
|
-
magmoms=magmoms,
|
173
|
-
collinear=collinear,
|
174
|
-
)
|
175
|
-
|
176
|
-
self.magnetic_elements = magnetic_elements
|
177
|
-
self.kmesh = kmesh
|
178
|
-
|
179
|
-
num_terms = 4 if collinear else 18
|
180
|
-
self._exchange_values = np.empty((0, 0, num_terms), dtype=float)
|
181
|
-
|
182
|
-
@property
|
183
|
-
def magnetic_elements(self):
|
184
|
-
"""List of magnetic elements in the structure."""
|
185
|
-
return self._magnetic_elements
|
186
|
-
|
187
|
-
@magnetic_elements.setter
|
188
|
-
def magnetic_elements(self, value):
|
189
|
-
from .structure import validate_symbols
|
190
|
-
|
191
|
-
symbols = validate_symbols(value)
|
192
|
-
for symbol in symbols:
|
193
|
-
if symbol not in self.elements:
|
194
|
-
raise ValueError(f"Symbol '{symbol}' not in 'elements'.")
|
195
|
-
|
196
|
-
self._magnetic_elements = symbols
|
197
|
-
self._set_index_pairs()
|
198
|
-
|
199
|
-
@property
|
200
|
-
def interacting_pairs(self):
|
201
|
-
"""List of pairs of interacting magnetic sites."""
|
202
|
-
return self._pairs
|
203
|
-
|
204
|
-
def _set_index_pairs(self):
|
205
|
-
from itertools import combinations_with_replacement
|
206
|
-
|
207
|
-
magnetic_elements = self.magnetic_elements
|
208
|
-
elements = self.elements
|
209
|
-
indices = [
|
210
|
-
i for i, symbol in enumerate(elements) if symbol in magnetic_elements
|
211
|
-
]
|
212
|
-
index_pairs = list(combinations_with_replacement(indices, 2))
|
213
|
-
index_spin = np.sort(np.unique(index_pairs))
|
214
|
-
|
215
|
-
self._pairs = index_pairs
|
216
|
-
self._index_spin = index_spin
|
217
|
-
|
218
|
-
@property
|
219
|
-
def kmesh(self):
|
220
|
-
"""K-point mesh dimensions for sampling the Brillouin zone."""
|
221
|
-
return self._kmesh
|
222
|
-
|
223
|
-
@kmesh.setter
|
224
|
-
def kmesh(self, values):
|
225
|
-
try:
|
226
|
-
the_kmesh = [int(k) for k in values]
|
227
|
-
except (ValueError, TypeError):
|
228
|
-
raise ValueError("Argument must be an iterable with 'int' elements.")
|
229
|
-
if len(the_kmesh) != 3:
|
230
|
-
raise ValueError("Argument must be of length 3.")
|
231
|
-
if any(k < 1 for k in the_kmesh):
|
232
|
-
raise ValueError("Argument must contain only positive numbers.")
|
233
|
-
|
234
|
-
self._kmesh = the_kmesh
|
235
|
-
|
236
|
-
@property
|
237
|
-
def vectors(self):
|
238
|
-
"""Array of interaction vectors between magnetic sites."""
|
239
|
-
return self._exchange_values[:, :, :3]
|
240
|
-
|
241
|
-
def set_vectors(self, values=None, cartesian=False):
|
242
|
-
"""
|
243
|
-
Set the interaction vectors between magnetic sites.
|
244
|
-
|
245
|
-
Parameters
|
246
|
-
----------
|
247
|
-
values : array_like, optional
|
248
|
-
Array of interaction vectors
|
249
|
-
cartesian : bool, optional
|
250
|
-
Whether the vectors are in Cartesian coordinates, default False
|
251
|
-
"""
|
252
|
-
try:
|
253
|
-
pairs = self._pairs
|
254
|
-
except AttributeError:
|
255
|
-
raise AttributeError("'magnetic_elements' attribute has not been set yet.")
|
256
|
-
else:
|
257
|
-
n_pairs = len(pairs)
|
258
|
-
|
259
|
-
if values is None:
|
260
|
-
i, j = zip(*pairs)
|
261
|
-
positions = self.positions
|
262
|
-
base_vectors = positions[i, :] - positions[j, :]
|
263
|
-
grid = generate_grid(self.kmesh)
|
264
|
-
vectors = base_vectors[:, None, :] + grid[None, :, :]
|
265
|
-
m_interactions = np.prod(self.kmesh)
|
266
|
-
else:
|
267
|
-
vectors = get_attribute_array(values, "vectors", dtype=float)
|
268
|
-
if vectors.ndim != 3 or vectors.shape[::2] != (n_pairs, 3):
|
269
|
-
raise ValueError(
|
270
|
-
f"'vectors' must have the shape (n, m, ), where n={n_pairs} is the number of\n"
|
271
|
-
"pairs of interacting species."
|
272
|
-
)
|
273
|
-
if cartesian:
|
274
|
-
vectors = np.linalg.solve(self.cell.T, vectors.swapaxes(1, -1))
|
275
|
-
vectors = vectors.swapaxes(-1, 1)
|
276
|
-
m_interactions = vectors.shape[1]
|
277
|
-
|
278
|
-
shape = (
|
279
|
-
(n_pairs, m_interactions, 4)
|
280
|
-
if self.collinear
|
281
|
-
else (n_pairs, m_interactions, 18)
|
282
|
-
)
|
283
|
-
exchange_values = np.zeros(shape, dtype=float)
|
284
|
-
exchange_values[:, :, :3] = vectors
|
285
|
-
self._exchange_values = exchange_values
|
286
|
-
|
287
|
-
def _get_neighbor_indices(self, neighbors, tol=1e-4):
|
288
|
-
"""
|
289
|
-
Get indices of neighbor pairs based on distance.
|
290
|
-
|
291
|
-
Parameters
|
292
|
-
----------
|
293
|
-
neighbors : list
|
294
|
-
List of neighbor shells to consider
|
295
|
-
tol : float, optional
|
296
|
-
Distance tolerance for neighbor shell assignment, default 1e-4
|
297
|
-
|
298
|
-
Returns
|
299
|
-
-------
|
300
|
-
tuple
|
301
|
-
Indices corresponding to the specified neighbor shells
|
302
|
-
"""
|
303
|
-
distance = np.linalg.norm(self.vectors @ self.cell, axis=-1)
|
304
|
-
distance = round_to_precision(distance, tol)
|
305
|
-
neighbors_distance = np.unique(np.sort(distance))
|
306
|
-
indices = np.where(
|
307
|
-
distance[:, :, None] == neighbors_distance[neighbors][None, None, :]
|
308
|
-
)
|
309
|
-
|
310
|
-
return indices
|
311
|
-
|
312
|
-
def set_exchange_array(self, name, values, neighbors=None, tol=1e-4):
|
313
|
-
"""
|
314
|
-
Set exchange interaction values for specified neighbors.
|
315
|
-
|
316
|
-
Parameters
|
317
|
-
----------
|
318
|
-
name : str
|
319
|
-
Type of exchange interaction ('Jiso', 'Biquad', 'DMI', or 'Jani')
|
320
|
-
values : array_like
|
321
|
-
Exchange interaction values
|
322
|
-
neighbors : list, optional
|
323
|
-
List of neighbor shells to assign values to
|
324
|
-
tol : float, optional
|
325
|
-
Distance tolerance for neighbor shell assignment, default 1e-4
|
326
|
-
"""
|
327
|
-
if self.vectors.size == 0:
|
328
|
-
raise AttributeError("The intraction vectors must be set first.")
|
329
|
-
|
330
|
-
array = get_attribute_array(values, name, dtype=float)
|
331
|
-
|
332
|
-
if neighbors is not None:
|
333
|
-
if len(array) != len(neighbors):
|
334
|
-
raise ValueError(
|
335
|
-
"The number of neighbors and exchange values does not coincide."
|
336
|
-
)
|
337
|
-
*array_indices, value_indices = self._get_neighbor_indices(
|
338
|
-
list(neighbors), tol=tol
|
339
|
-
)
|
340
|
-
else:
|
341
|
-
if self._exchange_values.shape[:2] != array.shape[:2]:
|
342
|
-
raise ValueError(
|
343
|
-
f"The shape of the array is incompatible with '{self.exchange_values.shape}'"
|
344
|
-
)
|
345
|
-
array_indices, value_indices = (
|
346
|
-
[slice(None), slice(None)],
|
347
|
-
(slice(None), slice(None)),
|
348
|
-
)
|
349
|
-
|
350
|
-
if name == "Jiso":
|
351
|
-
self._exchange_values[*array_indices, 3] = array[value_indices]
|
352
|
-
elif name == "Biquad":
|
353
|
-
self._exchange_values[*array_indices, 4:6] = array[value_indices]
|
354
|
-
elif name == "DMI":
|
355
|
-
self._exchange_values[*array_indices, 6:9] = array[value_indices]
|
356
|
-
elif name == "Jani":
|
357
|
-
self._exchange_values[*array_indices, 9:] = array[value_indices].reshape(
|
358
|
-
array.shape[:2] + (9,)
|
359
|
-
)
|
360
|
-
else:
|
361
|
-
raise ValueError(f"Unrecognized exchange array name: '{name}'.")
|
362
|
-
|
363
|
-
@property
|
364
|
-
def Jiso(self):
|
365
|
-
return self._exchange_values[:, :, 3]
|
366
|
-
|
367
|
-
@property
|
368
|
-
def Biquad(self):
|
369
|
-
return self._exchange_values[:, :, 4:6]
|
370
|
-
|
371
|
-
@property
|
372
|
-
def DMI(self):
|
373
|
-
return self._exchange_values[:, :, 6:9]
|
374
|
-
|
375
|
-
@property
|
376
|
-
def Jani(self):
|
377
|
-
matrix_shape = self._exchange_values.shape[:2] + (3, 3)
|
378
|
-
return self._exchange_values[:, :, 9:].reshape(matrix_shape)
|
379
|
-
|
380
|
-
def exchange_tensor(self, anisotropic=True):
|
381
|
-
"""
|
382
|
-
Compute the exchange interaction tensor.
|
383
|
-
|
384
|
-
Parameters
|
385
|
-
----------
|
386
|
-
anisotropic : bool, optional
|
387
|
-
Whether to include anisotropic interactions, default True
|
388
|
-
|
389
|
-
Returns
|
390
|
-
-------
|
391
|
-
numpy.ndarray
|
392
|
-
Exchange interaction tensor
|
393
|
-
"""
|
394
|
-
shape = self._exchange_values.shape[:2] + (3, 3)
|
395
|
-
tensor = np.zeros(shape, dtype=float)
|
396
|
-
|
397
|
-
if anisotropic and not self.collinear:
|
398
|
-
tensor += self._exchange_values[:, :, 9:].reshape(shape)
|
399
|
-
pos_indices = ([1, 2, 0], [2, 0, 1])
|
400
|
-
neg_indices = ([2, 0, 1], [1, 2, 0])
|
401
|
-
tensor[:, :, *pos_indices] += self._exchange_values[:, :, 6:9]
|
402
|
-
tensor[:, :, *neg_indices] -= self._exchange_values[:, :, 6:9]
|
403
|
-
diag_indices = ([0, 1, 2], [0, 1, 2])
|
404
|
-
tensor[:, :, *diag_indices] += self._exchange_values[:, :, 3, None]
|
405
|
-
|
406
|
-
return tensor
|
407
|
-
|
408
|
-
def Jq(self, kpoints, anisotropic=True):
|
409
|
-
"""
|
410
|
-
Compute the exchange interactions in reciprocal space.
|
411
|
-
|
412
|
-
Parameters
|
413
|
-
----------
|
414
|
-
kpoints : array_like
|
415
|
-
k-points at which to evaluate the exchange interactions
|
416
|
-
anisotropic : bool, optional
|
417
|
-
Whether to include anisotropic interactions, default True
|
418
|
-
|
419
|
-
Returns
|
420
|
-
-------
|
421
|
-
numpy.ndarray
|
422
|
-
Exchange interactions in reciprocal space
|
423
|
-
"""
|
424
|
-
vectors = self._exchange_values[:, :, :3].copy()
|
425
|
-
tensor = self.exchange_tensor(anisotropic=anisotropic)
|
426
|
-
|
427
|
-
if self._Q is not None:
|
428
|
-
phi = 2 * np.pi * vectors.round(3).astype(int) @ self._Q
|
429
|
-
rv = np.einsum("ij,k->ijk", phi, self._n)
|
430
|
-
R = (
|
431
|
-
Rotation.from_rotvec(rv.reshape(-1, 3))
|
432
|
-
.as_matrix()
|
433
|
-
.reshape(vectors.shape[:2] + (3, 3))
|
434
|
-
)
|
435
|
-
np.einsum("nmij,nmjk->nmik", tensor, R, out=tensor)
|
436
|
-
|
437
|
-
exp_summand = np.exp(2j * np.pi * vectors @ kpoints.T)
|
438
|
-
Jexp = exp_summand[:, :, :, None, None] * tensor[:, :, None]
|
439
|
-
Jq = np.sum(Jexp, axis=1)
|
440
|
-
|
441
|
-
pairs = np.array(self._pairs)
|
442
|
-
idx = np.where(pairs[:, 0] == pairs[:, 1])
|
443
|
-
Jq[idx] /= 2
|
444
|
-
|
445
|
-
return Jq
|
446
|
-
|
447
|
-
def Hq(self, kpoints, anisotropic=True, u=uz):
|
448
|
-
"""
|
449
|
-
Compute the magnon Hamiltonian in reciprocal space.
|
450
|
-
|
451
|
-
Parameters
|
452
|
-
----------
|
453
|
-
kpoints : array_like
|
454
|
-
k-points at which to evaluate the Hamiltonian
|
455
|
-
anisotropic : bool, optional
|
456
|
-
Whether to include anisotropic interactions, default True
|
457
|
-
u : array_like, optional
|
458
|
-
Reference direction for spin quantization axis
|
459
|
-
|
460
|
-
Returns
|
461
|
-
-------
|
462
|
-
numpy.ndarray
|
463
|
-
Magnon Hamiltonian matrix at each k-point
|
464
|
-
"""
|
465
|
-
if self.collinear:
|
466
|
-
magmoms = np.zeros((self._index_spin.size, 3))
|
467
|
-
magmoms[:, 2] = self.magmoms[self._index_spin]
|
468
|
-
else:
|
469
|
-
magmoms = self.magmoms[self._index_spin]
|
470
|
-
magmoms /= np.linalg.norm(magmoms, axis=-1)[:, None]
|
471
|
-
|
472
|
-
U, V = get_rotation_arrays(magmoms, u=u)
|
473
|
-
|
474
|
-
J0 = self.Jq(np.zeros((1, 3)), anisotropic=anisotropic)
|
475
|
-
J0 = -Hermitize(J0)[:, :, 0]
|
476
|
-
Jq = -Hermitize(self.Jq(kpoints, anisotropic=anisotropic))
|
477
|
-
|
478
|
-
C = np.diag(np.einsum("ix,ijxy,jy->i", V, 2 * J0, V))
|
479
|
-
B = np.einsum("ix,ijkxy,jy->kij", U, Jq, U)
|
480
|
-
A1 = np.einsum("ix,ijkxy,jy->kij", U, Jq, U.conj())
|
481
|
-
A2 = np.einsum("ix,ijkxy,jy->kij", U.conj(), Jq, U)
|
482
|
-
|
483
|
-
return np.block([[A1 - C, B], [B.swapaxes(-1, -2).conj(), A2 - C]])
|
484
|
-
|
485
|
-
def _magnon_energies(self, kpoints, anisotropic=True, u=uz):
|
486
|
-
H = self.Hq(kpoints, anisotropic=anisotropic, u=u)
|
487
|
-
n = H.shape[-1] // 2
|
488
|
-
I = np.eye(n)
|
489
|
-
|
490
|
-
min_eig = 0.0
|
491
|
-
try:
|
492
|
-
K = np.linalg.cholesky(H)
|
493
|
-
except np.linalg.LinAlgError:
|
494
|
-
try:
|
495
|
-
K = np.linalg.cholesky(H + 1e-6 * np.eye(2 * n))
|
496
|
-
except np.linalg.LinAlgError:
|
497
|
-
from warnings import warn
|
498
|
-
|
499
|
-
min_eig = np.min(np.linalg.eigvalsh(H))
|
500
|
-
K = np.linalg.cholesky(H - (min_eig - 1e-6) * np.eye(2 * n))
|
501
|
-
warn(
|
502
|
-
f"WARNING: The system may be far from the magnetic ground-state. Minimum eigenvalue: {min_eig}. The magnon energies might be unphysical."
|
503
|
-
)
|
504
|
-
|
505
|
-
g = np.block([[1 * I, 0 * I], [0 * I, -1 * I]])
|
506
|
-
KH = K.swapaxes(-1, -2).conj()
|
507
|
-
|
508
|
-
return np.linalg.eigvalsh(KH @ g @ K)[:, n:] + min_eig
|
509
|
-
|
510
|
-
def get_magnon_bands(
|
511
|
-
self,
|
512
|
-
kpoints: np.array = np.array([]),
|
513
|
-
path: str = None,
|
514
|
-
npoints: int = 300,
|
515
|
-
special_points: dict = None,
|
516
|
-
tol: float = 2e-4,
|
517
|
-
pbc: tuple = None,
|
518
|
-
cartesian: bool = False,
|
519
|
-
labels: list = None,
|
520
|
-
anisotropic: bool = True,
|
521
|
-
u: np.array = uz,
|
522
|
-
):
|
523
|
-
pbc = self._pbc if pbc is None else pbc
|
524
|
-
|
525
|
-
if kpoints.size == 0:
|
526
|
-
from ase.cell import Cell
|
527
|
-
|
528
|
-
bandpath = Cell(self._cell).bandpath(
|
529
|
-
path=path,
|
530
|
-
npoints=npoints,
|
531
|
-
special_points=special_points,
|
532
|
-
eps=tol,
|
533
|
-
pbc=pbc,
|
534
|
-
)
|
535
|
-
kpoints = bandpath.kpts
|
536
|
-
spk = bandpath.special_points
|
537
|
-
spk[r"$\Gamma$"] = spk.pop("G", np.zeros(3))
|
538
|
-
labels = [
|
539
|
-
(i, symbol)
|
540
|
-
for symbol in spk
|
541
|
-
for i in np.where((kpoints == spk[symbol]).all(axis=1))[0]
|
542
|
-
]
|
543
|
-
elif cartesian:
|
544
|
-
kpoints = np.linalg.solve(self._cell.T, kpoints.T).T
|
545
|
-
|
546
|
-
bands = self._magnon_energies(kpoints, anisotropic=anisotropic, u=u)
|
547
|
-
|
548
|
-
return labels, bands
|
549
|
-
|
550
|
-
def plot_magnon_bands(self, **kwargs):
|
551
|
-
"""
|
552
|
-
Plot magnon band structure.
|
553
|
-
|
554
|
-
Parameters
|
555
|
-
----------
|
556
|
-
**kwargs
|
557
|
-
Additional keyword arguments passed to get_magnon_bands and plotting functions
|
558
|
-
"""
|
559
|
-
filename = kwargs.pop("filename", None)
|
560
|
-
kpath, bands = self.get_magnon_bands(**kwargs)
|
561
|
-
bands_plot = BandsPlot(bands, kpath)
|
562
|
-
bands_plot.plot(filename=filename)
|
563
|
-
|
564
|
-
@classmethod
|
565
|
-
def load_tb2j(
|
566
|
-
cls,
|
567
|
-
pickle_file: str = "TB2J.pickle",
|
568
|
-
pbc: tuple = (True, True, True),
|
569
|
-
anisotropic: bool = False,
|
570
|
-
quadratic: bool = False,
|
571
|
-
):
|
572
|
-
from pickle import load
|
573
|
-
|
574
|
-
try:
|
575
|
-
with open(pickle_file, "rb") as File:
|
576
|
-
content = load(File)
|
577
|
-
except FileNotFoundError:
|
578
|
-
raise FileNotFoundError(
|
579
|
-
f"No such file or directory: '{pickle_file}'. Please provide a valid .pickle file."
|
580
|
-
)
|
581
|
-
else:
|
582
|
-
correct_content(content)
|
583
|
-
|
584
|
-
magmoms = content["magmoms"] if content["colinear"] else content["spinat"]
|
585
|
-
magnetic_elements = {
|
586
|
-
content["atoms"].numbers[i]
|
587
|
-
for i, j in enumerate(content["index_spin"])
|
588
|
-
if j > -1
|
589
|
-
}
|
590
|
-
|
591
|
-
exchange = cls(
|
592
|
-
atoms=content["atoms"],
|
593
|
-
magmoms=magmoms,
|
594
|
-
pbc=pbc,
|
595
|
-
collinear=content["colinear"],
|
596
|
-
magnetic_elements=magnetic_elements,
|
597
|
-
)
|
598
|
-
|
599
|
-
num_pairs = len(exchange.interacting_pairs)
|
600
|
-
bkeys = branched_keys(content["distance_dict"].keys(), num_pairs)
|
601
|
-
|
602
|
-
vectors = [
|
603
|
-
[content["distance_dict"][key][0] for key in branch] for branch in bkeys
|
604
|
-
]
|
605
|
-
exchange.set_vectors(vectors, cartesian=True)
|
606
|
-
Jiso = [[content["exchange_Jdict"][key] for key in branch] for branch in bkeys]
|
607
|
-
exchange.set_exchange_array("Jiso", Jiso)
|
608
|
-
|
609
|
-
if not content["colinear"] and anisotropic:
|
610
|
-
Jani = [[content["Jani_dict"][key] for key in branch] for branch in bkeys]
|
611
|
-
exchange.set_exchange_array("Jani", Jani)
|
612
|
-
DMI = [[content["dmi_ddict"][key] for key in branch] for branch in bkeys]
|
613
|
-
exchange.set_exchange_array("DMI", DMI)
|
614
|
-
if quadratic:
|
615
|
-
Biquad = [
|
616
|
-
[content["biquadratic_Jdict"][key] for key in branch]
|
617
|
-
for branch in bkeys
|
618
|
-
]
|
619
|
-
exchange.set_exchange_array("Biquad", Biquad)
|
620
|
-
|
621
|
-
return exchange
|
622
|
-
|
623
|
-
|
624
|
-
def plot_tb2j_magnon_bands(
|
625
|
-
pickle_file: str = "TB2J.pickle",
|
626
|
-
path: str = None,
|
627
|
-
npoints: int = 300,
|
628
|
-
special_points: dict = None,
|
629
|
-
anisotropic: bool = False,
|
630
|
-
quadratic: bool = False,
|
631
|
-
pbc: tuple = (True, True, True),
|
632
|
-
filename: str = None,
|
633
|
-
):
|
634
|
-
"""
|
635
|
-
Load TB2J data and plot magnon band structure in one step.
|
636
|
-
|
637
|
-
This is a convenience function that combines loading TB2J data and plotting
|
638
|
-
magnon bands. It first loads the magnetic structure and exchange interactions
|
639
|
-
from a TB2J pickle file, then calculates and plots the magnon band structure.
|
640
|
-
|
641
|
-
Parameters
|
642
|
-
----------
|
643
|
-
pickle_file : str, optional
|
644
|
-
Path to the TB2J pickle file, default "TB2J.pickle"
|
645
|
-
path : str, optional
|
646
|
-
High-symmetry k-point path for band structure plot
|
647
|
-
(e.g., "GXMG" for a square lattice)
|
648
|
-
npoints : int, optional
|
649
|
-
Number of k-points for band structure calculation, default 300
|
650
|
-
special_points : dict, optional
|
651
|
-
Dictionary of special k-points for custom paths
|
652
|
-
anisotropic : bool, optional
|
653
|
-
Whether to include anisotropic interactions, default False
|
654
|
-
quadratic : bool, optional
|
655
|
-
Whether to include biquadratic interactions, default False
|
656
|
-
pbc : tuple, optional
|
657
|
-
Periodic boundary conditions, default (True, True, True)
|
658
|
-
filename : str, optional
|
659
|
-
If provided, save the plot to this file
|
660
|
-
|
661
|
-
Returns
|
662
|
-
-------
|
663
|
-
exchange : ExchangeIO
|
664
|
-
The ExchangeIO instance containing the loaded data and plot
|
665
|
-
|
666
|
-
Example
|
667
|
-
-------
|
668
|
-
>>> # Basic usage with default parameters
|
669
|
-
>>> plot_tb2j_magnon_bands()
|
670
|
-
|
671
|
-
>>> # Custom path and saving to file
|
672
|
-
>>> plot_tb2j_magnon_bands(
|
673
|
-
... path="GXMG",
|
674
|
-
... anisotropic=True,
|
675
|
-
... filename="magnon_bands.png"
|
676
|
-
... )
|
677
|
-
"""
|
678
|
-
# Load the TB2J data
|
679
|
-
exchange = ExchangeIO.load_tb2j(
|
680
|
-
pickle_file=pickle_file, pbc=pbc, anisotropic=anisotropic, quadratic=quadratic
|
681
|
-
)
|
682
|
-
|
683
|
-
# Plot the magnon bands
|
684
|
-
exchange.plot_magnon_bands(
|
685
|
-
path=path, npoints=npoints, special_points=special_points, filename=filename
|
686
|
-
)
|
687
|
-
|
688
|
-
return exchange
|