TB2J 0.9.9rc19__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.
Files changed (29) hide show
  1. TB2J/Jdownfolder.py +2 -3
  2. TB2J/MAEGreen.py +17 -22
  3. TB2J/exchange_params.py +1 -6
  4. TB2J/interfaces/siesta_interface.py +4 -15
  5. TB2J/io_exchange/__init__.py +0 -2
  6. TB2J/io_exchange/io_exchange.py +5 -2
  7. TB2J/mathutils/__init__.py +0 -1
  8. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/abacus2J.py +2 -6
  9. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/siesta2J.py +1 -5
  10. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/wann2J.py +2 -6
  11. {tb2j-0.9.9rc19.dist-info → tb2j-0.9.9.1.dist-info}/METADATA +3 -2
  12. {tb2j-0.9.9rc19.dist-info → tb2j-0.9.9.1.dist-info}/RECORD +23 -29
  13. TB2J/magnon/__init__.py +0 -3
  14. TB2J/magnon/io_exchange2.py +0 -688
  15. TB2J/magnon/plot.py +0 -58
  16. TB2J/magnon/structure.py +0 -348
  17. TB2J/mathutils/magnons.py +0 -45
  18. tb2j-0.9.9rc19.data/scripts/TB2J_magnon2.py +0 -78
  19. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_downfold.py +0 -0
  20. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_eigen.py +0 -0
  21. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_magnon.py +0 -0
  22. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_magnon_dos.py +0 -0
  23. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_merge.py +0 -0
  24. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_rotate.py +0 -0
  25. {tb2j-0.9.9rc19.data → tb2j-0.9.9.1.data}/scripts/TB2J_rotateDM.py +0 -0
  26. {tb2j-0.9.9rc19.dist-info → tb2j-0.9.9.1.dist-info}/WHEEL +0 -0
  27. {tb2j-0.9.9rc19.dist-info → tb2j-0.9.9.1.dist-info}/entry_points.txt +0 -0
  28. {tb2j-0.9.9rc19.dist-info → tb2j-0.9.9.1.dist-info}/licenses/LICENSE +0 -0
  29. {tb2j-0.9.9rc19.dist-info → tb2j-0.9.9.1.dist-info}/top_level.txt +0 -0
@@ -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