TB2J 0.9.12.18__py3-none-any.whl → 0.9.12.22__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 (40) hide show
  1. TB2J/MAE.py +8 -1
  2. TB2J/MAEGreen.py +0 -2
  3. TB2J/exchange.py +11 -4
  4. TB2J/exchangeCL2.py +2 -0
  5. TB2J/exchange_params.py +24 -0
  6. TB2J/green.py +15 -3
  7. TB2J/interfaces/abacus/gen_exchange_abacus.py +2 -1
  8. TB2J/io_exchange/__init__.py +19 -1
  9. TB2J/io_exchange/edit.py +594 -0
  10. TB2J/io_exchange/io_exchange.py +238 -74
  11. TB2J/io_exchange/io_tomsasd.py +4 -3
  12. TB2J/io_exchange/io_txt.py +72 -7
  13. TB2J/io_exchange/io_vampire.py +1 -1
  14. TB2J/io_merge.py +60 -40
  15. TB2J/magnon/magnon3.py +27 -2
  16. TB2J/mathutils/rotate_spin.py +7 -7
  17. TB2J/mycfr.py +11 -11
  18. TB2J/plot.py +26 -0
  19. TB2J/scripts/TB2J_edit.py +403 -0
  20. TB2J/scripts/TB2J_plot_exchange.py +48 -0
  21. TB2J/spinham/hamiltonian.py +156 -13
  22. TB2J/spinham/hamiltonian_terms.py +40 -1
  23. TB2J/spinham/spin_xml.py +40 -8
  24. TB2J/symmetrize_J.py +138 -7
  25. TB2J/tests/test_cli_remove_sublattice.py +33 -0
  26. TB2J/tests/test_cli_toggle_exchange.py +50 -0
  27. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/METADATA +7 -3
  28. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/RECORD +32 -35
  29. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/WHEEL +1 -1
  30. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/entry_points.txt +2 -0
  31. TB2J/.gitignore +0 -5
  32. TB2J/agent_files/debug_spinphon_fd/debug_main.py +0 -156
  33. TB2J/agent_files/debug_spinphon_fd/test_compute_dJdx.py +0 -272
  34. TB2J/agent_files/debug_spinphon_fd/test_ispin0_only.py +0 -120
  35. TB2J/agent_files/debug_spinphon_fd/test_no_d2j.py +0 -31
  36. TB2J/agent_files/debug_spinphon_fd/test_with_d2j.py +0 -28
  37. TB2J/interfaces/abacus/test_read_HRSR.py +0 -43
  38. TB2J/interfaces/abacus/test_read_stru.py +0 -32
  39. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/licenses/LICENSE +0 -0
  40. {tb2j-0.9.12.18.dist-info → tb2j-0.9.12.22.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Command-line interface for TB2J_edit.
4
+
5
+ Usage:
6
+ TB2J_edit load -i INPUT.pickle -o OUTPUT_DIR
7
+ TB2J_edit set-anisotropy -i INPUT.pickle -s Sm 5.0 -d "0 0 1" -o OUTPUT_DIR
8
+ TB2J_edit set-anisotropy -i INPUT.pickle -s Sm 5.0 -s Fe 1.0 -o OUTPUT_DIR
9
+ TB2J_edit toggle-dmi -i INPUT.pickle --disable -o OUTPUT_DIR
10
+ TB2J_edit toggle-jani -i INPUT.pickle --disable -o OUTPUT_DIR
11
+ TB2J_edit toggle-exchange -i INPUT.pickle --disable -o OUTPUT_DIR
12
+ TB2J_edit symmetrize -i INPUT.pickle -S STRUCTURE.cif -o OUTPUT_DIR
13
+ """
14
+
15
+ import argparse
16
+ import sys
17
+
18
+ import numpy as np
19
+
20
+
21
+ def cmd_load(args):
22
+ """Load TB2J results and save to output directory."""
23
+ from TB2J.io_exchange.edit import load, save
24
+
25
+ print(f"Loading TB2J results from: {args.input}")
26
+ spinio = load(args.input)
27
+ print(f" Loaded {len(spinio.atoms)} atoms")
28
+ print(f" Has DMI: {spinio.has_dmi}")
29
+ print(f" Has Jani: {spinio.has_bilinear}")
30
+
31
+ print(f"Saving to: {args.output}")
32
+ save(spinio, args.output)
33
+ print(" Done!")
34
+
35
+
36
+ def cmd_set_anisotropy(args):
37
+ """Set single ion anisotropy for one or more species."""
38
+ from TB2J.io_exchange.edit import load, save, set_anisotropy
39
+
40
+ print(f"Loading TB2J results from: {args.input}")
41
+ spinio = load(args.input)
42
+
43
+ # Parse species specifications
44
+ # args.species is a list of lists: [['Sm', '5.0'], ['Fe', '1.0']]
45
+ # or with direction: [['Sm', '5.0', '0,0,1'], ['Fe', '1.0']]
46
+ specs = []
47
+ default_dir = None
48
+ if args.dir:
49
+ default_dir = [float(x) for x in args.dir.split()]
50
+
51
+ for spec_list in args.species:
52
+ if len(spec_list) < 2:
53
+ print(
54
+ f"Error: Invalid specification {spec_list}. Expected: species k1 [dir]"
55
+ )
56
+ sys.exit(1)
57
+
58
+ species = spec_list[0]
59
+ k1_val = float(spec_list[1])
60
+ k1_eV = k1_val * 1e-3 if args.mev else k1_val
61
+
62
+ # Check if direction vector is provided
63
+ k1dir = default_dir
64
+ if len(spec_list) >= 3:
65
+ # Third element is the direction vector
66
+ dir_str = spec_list[2].replace(",", " ")
67
+ k1dir = [float(x) for x in dir_str.split()]
68
+
69
+ specs.append((species, k1_eV, k1dir))
70
+
71
+ print(f"Setting anisotropy for {len(specs)} species:")
72
+ for species, k1, k1dir in specs:
73
+ unit = "meV" if args.mev else "eV"
74
+ k1_display = k1 * 1000 if args.mev else k1
75
+ print(f" {species}: k1 = {k1_display:.4f} {unit}", end="")
76
+ if k1dir is not None:
77
+ print(f", k1dir = {k1dir}")
78
+ else:
79
+ print()
80
+
81
+ for species, k1, k1dir in specs:
82
+ set_anisotropy(spinio, species=species, k1=k1, k1dir=k1dir)
83
+
84
+ print(f"Saving to: {args.output}")
85
+ save(spinio, args.output)
86
+ print(" Done!")
87
+
88
+
89
+ def cmd_toggle_dmi(args):
90
+ """Toggle DMI on/off."""
91
+ from TB2J.io_exchange.edit import load, save, toggle_DMI
92
+
93
+ print(f"Loading TB2J results from: {args.input}")
94
+ spinio = load(args.input)
95
+
96
+ if args.disable:
97
+ enabled = False
98
+ action = "Disabling"
99
+ elif args.enable:
100
+ enabled = True
101
+ action = "Enabling"
102
+ else:
103
+ enabled = None
104
+ action = "Toggling"
105
+
106
+ print(f"{action} DMI...")
107
+ toggle_DMI(spinio, enabled=enabled)
108
+ print(f" DMI is now: {'enabled' if spinio.has_dmi else 'disabled'}")
109
+
110
+ print(f"Saving to: {args.output}")
111
+ save(spinio, args.output)
112
+ print(" Done!")
113
+
114
+
115
+ def cmd_toggle_jani(args):
116
+ """Toggle anisotropic exchange on/off."""
117
+ from TB2J.io_exchange.edit import load, save, toggle_Jani
118
+
119
+ print(f"Loading TB2J results from: {args.input}")
120
+ spinio = load(args.input)
121
+
122
+ if args.disable:
123
+ enabled = False
124
+ action = "Disabling"
125
+ elif args.enable:
126
+ enabled = True
127
+ action = "Enabling"
128
+ else:
129
+ enabled = None
130
+ action = "Toggling"
131
+
132
+ print(f"{action} anisotropic exchange...")
133
+ toggle_Jani(spinio, enabled=enabled)
134
+ print(f" Jani is now: {'enabled' if spinio.has_bilinear else 'disabled'}")
135
+
136
+ print(f"Saving to: {args.output}")
137
+ save(spinio, args.output)
138
+ print(" Done!")
139
+
140
+
141
+ def cmd_toggle_exchange(args):
142
+ """Toggle isotropic exchange on/off."""
143
+ from TB2J.io_exchange.edit import load, save, toggle_exchange
144
+
145
+ print(f"Loading TB2J results from: {args.input}")
146
+ spinio = load(args.input)
147
+
148
+ if args.disable:
149
+ enabled = False
150
+ action = "Disabling"
151
+ elif args.enable:
152
+ enabled = True
153
+ action = "Enabling"
154
+ else:
155
+ enabled = None
156
+ action = "Toggling"
157
+
158
+ print(f"{action} isotropic exchange...")
159
+ toggle_exchange(spinio, enabled=enabled)
160
+ print(
161
+ f" Isotropic exchange is now: {'enabled' if spinio.has_exchange else 'disabled'}"
162
+ )
163
+
164
+ print(f"Saving to: {args.output}")
165
+ save(spinio, args.output)
166
+ print(" Done!")
167
+
168
+
169
+ def cmd_symmetrize(args):
170
+ """Symmetrize exchange using a reference structure."""
171
+ from ase.io import read
172
+
173
+ from TB2J.io_exchange.edit import load, save, symmetrize_exchange
174
+
175
+ print(f"Loading TB2J results from: {args.input}")
176
+ spinio = load(args.input)
177
+
178
+ print(f"Loading reference structure from: {args.structure}")
179
+ atoms_ref = read(args.structure)
180
+ print(f" Loaded {len(atoms_ref)} atoms")
181
+
182
+ print(f"Symmetrizing exchange (symprec={args.symprec})...")
183
+ import warnings
184
+
185
+ with warnings.catch_warnings():
186
+ warnings.simplefilter("ignore")
187
+ symmetrize_exchange(spinio, atoms=atoms_ref, symprec=args.symprec)
188
+ print(" Done!")
189
+
190
+ print(f"Saving to: {args.output}")
191
+ save(spinio, args.output)
192
+ print(" Done!")
193
+
194
+
195
+ def cmd_remove_sublattice(args):
196
+ """Remove interactions associated with a sublattice."""
197
+ from TB2J.io_exchange.edit import load, remove_sublattice, save
198
+
199
+ print(f"Loading TB2J results from: {args.input}")
200
+ spinio = load(args.input)
201
+
202
+ print(f"Removing sublattice: {args.sublattice}")
203
+ remove_sublattice(spinio, args.sublattice)
204
+
205
+ print(f"Saving to: {args.output}")
206
+ save(spinio, args.output)
207
+ print(" Done!")
208
+
209
+
210
+ def cmd_info(args):
211
+ """Show information about TB2J results."""
212
+ from TB2J.io_exchange.edit import load
213
+
214
+ print(f"Loading TB2J results from: {args.input}")
215
+ spinio = load(args.input)
216
+
217
+ print("\n" + "=" * 60)
218
+ print("TB2J Results Information")
219
+ print("=" * 60)
220
+
221
+ print(f"\nAtoms: {len(spinio.atoms)}")
222
+ print(f"Chemical formula: {spinio.atoms.get_chemical_formula()}")
223
+
224
+ # Count atoms by species
225
+ symbols = spinio.atoms.get_chemical_symbols()
226
+ from collections import Counter
227
+
228
+ counts = Counter(symbols)
229
+ print(
230
+ f"Composition: {', '.join(f'{sym}: {count}' for sym, count in sorted(counts.items()))}"
231
+ )
232
+
233
+ # Magnetic atoms
234
+ mag_atoms = [i for i, idx in enumerate(spinio.index_spin) if idx >= 0]
235
+ print(f"Magnetic atoms: {len(mag_atoms)}")
236
+
237
+ # Exchange info
238
+ print("\nExchange parameters:")
239
+ print(
240
+ f" Isotropic exchange: {len(spinio.exchange_Jdict) if spinio.exchange_Jdict else 0} pairs"
241
+ )
242
+ print(f" DMI: {'enabled' if spinio.has_dmi else 'disabled'}")
243
+ print(f" Anisotropic exchange: {'enabled' if spinio.has_bilinear else 'disabled'}")
244
+
245
+ # Anisotropy
246
+ if spinio.k1:
247
+ print("\nSingle ion anisotropy:")
248
+ for i, (sym, idx) in enumerate(zip(symbols, spinio.index_spin)):
249
+ if idx >= 0 and idx < len(spinio.k1):
250
+ if spinio.k1[idx] != 0:
251
+ print(f" {sym}{i}: k1={spinio.k1[idx]:.4f} eV")
252
+ else:
253
+ print("\nSingle ion anisotropy: None")
254
+
255
+ # Cell info
256
+ print("\nCell parameters:")
257
+ cell = spinio.atoms.get_cell()
258
+ a, b, c = [np.linalg.norm(v) for v in cell]
259
+ print(f" a = {a:.4f} Å")
260
+ print(f" b = {b:.4f} Å")
261
+ print(f" c = {c:.4f} Å")
262
+
263
+ print("=" * 60)
264
+
265
+
266
+ def main():
267
+ """Main CLI entry point."""
268
+ parser = argparse.ArgumentParser(
269
+ prog="TB2J_edit",
270
+ description="Command-line interface for modifying TB2J results",
271
+ )
272
+ parser.set_defaults(func=None)
273
+
274
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
275
+
276
+ # load command
277
+ parser_load = subparsers.add_parser("load", help="Load and save TB2J results")
278
+ parser_load.add_argument("-i", "--input", required=True, help="Input pickle file")
279
+ parser_load.add_argument(
280
+ "-o", "--output", default="output", help="Output directory"
281
+ )
282
+ parser_load.set_defaults(func=cmd_load)
283
+
284
+ # set-anisotropy command
285
+ parser_aniso = subparsers.add_parser(
286
+ "set-anisotropy", help="Set single ion anisotropy", aliases=["set-aniso"]
287
+ )
288
+ parser_aniso.add_argument("-i", "--input", required=True, help="Input pickle file")
289
+ parser_aniso.add_argument("-o", "--output", required=True, help="Output directory")
290
+ parser_aniso.add_argument(
291
+ "-s",
292
+ "--species",
293
+ action="append",
294
+ nargs="+",
295
+ required=True,
296
+ help="Species and k1 value: -s species k1 (can be used multiple times)",
297
+ )
298
+ parser_aniso.add_argument("-d", "--dir", help='Default direction vector as "x y z"')
299
+ parser_aniso.add_argument(
300
+ "-m",
301
+ "--mev",
302
+ action="store_true",
303
+ help="Interpret k1 values in meV (default: eV)",
304
+ )
305
+ parser_aniso.set_defaults(func=cmd_set_anisotropy)
306
+
307
+ # toggle-dmi command
308
+ parser_dmi = subparsers.add_parser(
309
+ "toggle-dmi", help="Enable/disable DMI", aliases=["dmi"]
310
+ )
311
+ parser_dmi.add_argument("-i", "--input", required=True, help="Input pickle file")
312
+ parser_dmi.add_argument("-o", "--output", required=True, help="Output directory")
313
+ parser_dmi.add_argument("-e", "--enable", action="store_true", help="Enable DMI")
314
+ parser_dmi.add_argument("-d", "--disable", action="store_true", help="Disable DMI")
315
+ parser_dmi.set_defaults(func=cmd_toggle_dmi)
316
+
317
+ # toggle-jani command
318
+ parser_jani = subparsers.add_parser(
319
+ "toggle-jani", help="Enable/disable anisotropic exchange", aliases=["jani"]
320
+ )
321
+ parser_jani.add_argument("-i", "--input", required=True, help="Input pickle file")
322
+ parser_jani.add_argument("-o", "--output", required=True, help="Output directory")
323
+ parser_jani.add_argument(
324
+ "-e", "--enable", action="store_true", help="Enable anisotropic exchange"
325
+ )
326
+ parser_jani.add_argument(
327
+ "-d", "--disable", action="store_true", help="Disable anisotropic exchange"
328
+ )
329
+ parser_jani.set_defaults(func=cmd_toggle_jani)
330
+
331
+ parser_exchange = subparsers.add_parser(
332
+ "toggle-exchange",
333
+ help="Enable/disable isotropic exchange",
334
+ aliases=["exchange"],
335
+ )
336
+ parser_exchange.add_argument(
337
+ "-i", "--input", required=True, help="Input pickle file"
338
+ )
339
+ parser_exchange.add_argument(
340
+ "-o", "--output", required=True, help="Output directory"
341
+ )
342
+ parser_exchange.add_argument(
343
+ "-e", "--enable", action="store_true", help="Enable isotropic exchange"
344
+ )
345
+ parser_exchange.add_argument(
346
+ "-d", "--disable", action="store_true", help="Disable isotropic exchange"
347
+ )
348
+ parser_exchange.set_defaults(func=cmd_toggle_exchange)
349
+
350
+ # symmetrize command
351
+ parser_symm = subparsers.add_parser(
352
+ "symmetrize", help="Symmetrize exchange parameters", aliases=["symm"]
353
+ )
354
+ parser_symm.add_argument("-i", "--input", required=True, help="Input pickle file")
355
+ parser_symm.add_argument("-o", "--output", required=True, help="Output directory")
356
+ parser_symm.add_argument(
357
+ "-S",
358
+ "--structure",
359
+ required=True,
360
+ help="Reference structure file (CIF, VASP, etc.)",
361
+ )
362
+ parser_symm.add_argument(
363
+ "-p",
364
+ "--symprec",
365
+ type=float,
366
+ default=1e-3,
367
+ help="Symmetry precision in Angstrom (default: 1e-3)",
368
+ )
369
+ parser_symm.set_defaults(func=cmd_symmetrize)
370
+
371
+ parser_rm_sub = subparsers.add_parser(
372
+ "remove-sublattice",
373
+ help="Remove all interactions for a sublattice",
374
+ aliases=["rm-sub"],
375
+ )
376
+ parser_rm_sub.add_argument("-i", "--input", required=True, help="Input pickle file")
377
+ parser_rm_sub.add_argument("-o", "--output", required=True, help="Output directory")
378
+ parser_rm_sub.add_argument(
379
+ "-s", "--sublattice", required=True, help="Sublattice name (species symbol)"
380
+ )
381
+ parser_rm_sub.set_defaults(func=cmd_remove_sublattice)
382
+
383
+ # info command
384
+ parser_info = subparsers.add_parser(
385
+ "info", help="Show information about TB2J results"
386
+ )
387
+ parser_info.add_argument("-i", "--input", required=True, help="Input pickle file")
388
+ parser_info.set_defaults(func=cmd_info)
389
+
390
+ # Parse arguments
391
+ args = parser.parse_args()
392
+
393
+ # If no command specified, show help
394
+ if args.func is None:
395
+ parser.print_help()
396
+ sys.exit(1)
397
+
398
+ # Execute command
399
+ args.func(args)
400
+
401
+
402
+ if __name__ == "__main__":
403
+ main()
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import os
4
+
5
+ from TB2J.io_exchange.io_exchange import SpinIO
6
+
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(
10
+ description="Plot exchange parameters vs distance grouped by species."
11
+ )
12
+ parser.add_argument(
13
+ "-p",
14
+ "--path",
15
+ type=str,
16
+ default="./",
17
+ help="Path to TB2J results directory or TB2J.pickle file.",
18
+ )
19
+ parser.add_argument(
20
+ "-f",
21
+ "--fname",
22
+ type=str,
23
+ default="JvsR.pdf",
24
+ help="Output figure filename.",
25
+ )
26
+ parser.add_argument("--show", action="store_true", help="Show the plot.")
27
+ parser.add_argument(
28
+ "--no_species", action="store_true", help="Do not group by species."
29
+ )
30
+ args = parser.parse_args()
31
+
32
+ if os.path.isdir(args.path):
33
+ pickle_path = os.path.join(args.path, "TB2J.pickle")
34
+ else:
35
+ pickle_path = args.path
36
+
37
+ if not os.path.exists(pickle_path):
38
+ print(f"Error: {pickle_path} not found.")
39
+ return
40
+
41
+ spinio = SpinIO.load_pickle(
42
+ os.path.dirname(pickle_path), os.path.basename(pickle_path)
43
+ )
44
+ spinio.plot_JvsR(fname=args.fname, show=args.show, by_species=not args.no_species)
45
+
46
+
47
+ if __name__ == "__main__":
48
+ main()
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env python
2
- import numpy as np
3
- from collections.abc import Iterable
2
+ import json
4
3
  from collections import defaultdict
4
+ from collections.abc import Iterable
5
+
5
6
  import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ from ase.cell import Cell
6
9
  from ase.dft.kpoints import bandpath
10
+
7
11
  from TB2J.kpoints import monkhorst_pack
12
+
13
+ from .constants import gyromagnetic_ratio, mu_B
8
14
  from .hamiltonian_terms import (
9
- ZeemanTerm,
10
- UniaxialMCATerm,
11
- ExchangeTerm,
12
- DMITerm,
13
15
  BilinearTerm,
16
+ DMITerm,
17
+ ExchangeTerm,
18
+ SIATensorTerm,
19
+ UniaxialMCATerm,
20
+ ZeemanTerm,
14
21
  )
15
- from .constants import mu_B, gyromagnetic_ratio
16
- from .supercell import SupercellMaker
17
- from .spin_xml import SpinXmlParser, SpinXmlWriter
18
22
  from .plot import group_band_path
19
- from ase.cell import Cell
20
23
  from .qsolver import QSolver
21
- import json
24
+ from .spin_xml import SpinXmlParser, SpinXmlWriter
25
+ from .supercell import SupercellMaker
22
26
 
23
27
 
24
28
  class NumpyEncoder(json.JSONEncoder):
@@ -155,6 +159,10 @@ class SpinHamiltonian(object):
155
159
  self.has_bilinear = False
156
160
  self.bilinear_dict = None
157
161
 
162
+ # single ion anisotropy tensor term
163
+ self.has_sia_tensor = False
164
+ self.sia_tensor_dict = None
165
+
158
166
  # calculation parameters
159
167
 
160
168
  # calculation results
@@ -178,6 +186,122 @@ class SpinHamiltonian(object):
178
186
  self.iprim = iprim
179
187
  self.Rlist = Rlist
180
188
 
189
+ def remove_sublattice(self, sublattice_name):
190
+ """
191
+ Remove all magnetic interactions associated with a specific sublattice.
192
+ This includes:
193
+ - Single-ion anisotropy (SIA) for atoms in the sublattice.
194
+ - Exchange interactions (J) where i or j belongs to the sublattice.
195
+ - Dzyaloshinskii-Moriya interactions (DMI) where i or j belongs to the sublattice.
196
+ - Anisotropic exchange where i or j belongs to the sublattice.
197
+
198
+ :param sublattice_name: The name of the sublattice (species symbol) to remove.
199
+ """
200
+ symbols = self.lattice.get_chemical_symbols()
201
+ sublattice_indices = [
202
+ i
203
+ for i, (sym, idx) in enumerate(zip(symbols, self.index_spin))
204
+ if sym == sublattice_name and idx >= 0
205
+ ]
206
+
207
+ if not sublattice_indices:
208
+ print(
209
+ f"Warning: No magnetic atoms found for sublattice '{sublattice_name}'."
210
+ )
211
+ return
212
+
213
+ sublattice_spin_indices = set(self.index_spin[i] for i in sublattice_indices)
214
+
215
+ # Mark atoms as non-magnetic
216
+ for i in sublattice_indices:
217
+ self.index_spin[i] = -1
218
+
219
+ if self.has_uniaxial_anisotropy:
220
+ if self.k1 is not None:
221
+ for idx in sublattice_spin_indices:
222
+ self.k1[idx] = 0.0
223
+ if "UMCA" in self.hamiltonians:
224
+ self.set_uniaxial_mca(self.k1, self.k1dir)
225
+
226
+ old_to_new_spin_index = {}
227
+ current_spin_index = 0
228
+
229
+ for iatom, ispin in enumerate(self.index_spin):
230
+ if ispin >= 0:
231
+ if ispin not in old_to_new_spin_index:
232
+ old_to_new_spin_index[ispin] = current_spin_index
233
+ current_spin_index += 1
234
+ self.index_spin[iatom] = old_to_new_spin_index[ispin]
235
+
236
+ def reindex_interaction(interaction_dict):
237
+ if interaction_dict is None:
238
+ return None
239
+ new_dict = {}
240
+ for key, val in interaction_dict.items():
241
+ i, j = key[0], key[1]
242
+ if i in old_to_new_spin_index and j in old_to_new_spin_index:
243
+ new_i = old_to_new_spin_index[i]
244
+ new_j = old_to_new_spin_index[j]
245
+ if len(key) > 2:
246
+ new_key = (new_i, new_j) + key[2:]
247
+ else:
248
+ new_key = (new_i, new_j)
249
+ new_dict[new_key] = val
250
+ return new_dict
251
+
252
+ def filter_interaction(interaction_dict):
253
+ if interaction_dict is None:
254
+ return None
255
+ new_dict = {}
256
+ for key, val in interaction_dict.items():
257
+ i, j = key[0], key[1]
258
+ if (
259
+ i not in sublattice_spin_indices
260
+ and j not in sublattice_spin_indices
261
+ ):
262
+ new_dict[key] = val
263
+ return new_dict
264
+
265
+ if self.has_exchange:
266
+ self.exchange_Jdict = filter_interaction(self.exchange_Jdict)
267
+ self.exchange_Jdict = reindex_interaction(self.exchange_Jdict)
268
+ self.set_exchange_ijR(self.exchange_Jdict)
269
+
270
+ if self.has_dmi:
271
+ self.dmi_ddict = filter_interaction(self.dmi_ddict)
272
+ self.dmi_ddict = reindex_interaction(self.dmi_ddict)
273
+ self.set_dmi_ijR(self.dmi_ddict)
274
+
275
+ if self.has_bilinear:
276
+ self.bilinear_dict = filter_interaction(self.bilinear_dict)
277
+ self.bilinear_dict = reindex_interaction(self.bilinear_dict)
278
+ self.set_bilinear_ijR(self.bilinear_dict)
279
+
280
+ if self.has_uniaxial_anisotropy:
281
+ if self.k1 is not None:
282
+ new_k1 = np.zeros(current_spin_index)
283
+ new_k1dir = np.zeros((current_spin_index, 3))
284
+ new_k1dir[:, 2] = 1.0
285
+
286
+ for old_idx, new_idx in old_to_new_spin_index.items():
287
+ if old_idx < len(self.k1):
288
+ new_k1[new_idx] = self.k1[old_idx]
289
+ new_k1dir[new_idx] = self.k1dir[old_idx]
290
+
291
+ self.k1 = new_k1
292
+ self.k1dir = new_k1dir
293
+
294
+ if hasattr(self, "ms"):
295
+ new_ms = np.zeros(current_spin_index)
296
+ for old_idx, new_idx in old_to_new_spin_index.items():
297
+ if old_idx < len(self.ms):
298
+ new_ms[new_idx] = self.ms[old_idx]
299
+ self.ms = new_ms
300
+ self.nspin = current_spin_index
301
+
302
+ if self.has_uniaxial_anisotropy and "UMCA" in self.hamiltonians:
303
+ self.set_uniaxial_mca(self.k1, self.k1dir)
304
+
181
305
  def set_lattice(self, atoms, index_spin):
182
306
  self.lattice = atoms
183
307
  self.index_spin = index_spin
@@ -303,6 +427,17 @@ class SpinHamiltonian(object):
303
427
  umcaterm = UniaxialMCATerm(k1, k1dir, ms=self.ms)
304
428
  self.hamiltonians["UMCA"] = umcaterm
305
429
 
430
+ def set_sia_tensor(self, sia_tensor_dict):
431
+ """
432
+ Add single ion anisotropy tensor term.
433
+ H = - sum_i S_i^T A_i S_i
434
+ where A_i is a 3x3 tensor for each atom i.
435
+ """
436
+ self.has_sia_tensor = True
437
+ self.sia_tensor_dict = sia_tensor_dict
438
+ siaterm = SIATensorTerm(sia_tensor_dict, ms=self.ms)
439
+ self.hamiltonians["SIATensor"] = siaterm
440
+
306
441
  def add_Hamiltonian_term(self, Hamiltonian_term, name=None):
307
442
  """
308
443
  add Hamiltonian term which is not pre_defined.
@@ -380,6 +515,13 @@ class SpinHamiltonian(object):
380
515
  sc_bilinear_dict = smaker.sc_ijR(self.bilinear_dict, n_basis=len(self.pos))
381
516
  sc_ham.set_bilinear_ijR(sc_bilinear_dict)
382
517
 
518
+ if self.has_sia_tensor:
519
+ sc_sia_tensor_dict = {}
520
+ for i, A in self.sia_tensor_dict.items():
521
+ sc_i = smaker.sc_trans_invariant([i])[0]
522
+ sc_sia_tensor_dict[sc_i] = A
523
+ sc_ham.set_sia_tensor(sc_sia_tensor_dict)
524
+
383
525
  return sc_ham
384
526
 
385
527
  def calc_total_HijR(self):
@@ -457,7 +599,7 @@ class SpinHamiltonian(object):
457
599
  allevals.append(evals)
458
600
  # Plot band structure
459
601
  nbands = evals.shape[1]
460
- emin = np.min(evals[:, 0])
602
+ # emin = np.min(evals[:, 0])
461
603
  for i in range(nbands):
462
604
  ax.plot(xs, (evals[:, i]) / 1.6e-22, color=color)
463
605
 
@@ -474,7 +616,6 @@ class SpinHamiltonian(object):
474
616
  "xlist": xlist,
475
617
  "knames": knames,
476
618
  "X_for_highsym_kpoints": Xs,
477
- "knames": knames,
478
619
  "nbands": nbands,
479
620
  "nkpts": len(kptlist),
480
621
  "evals": allevals,
@@ -518,4 +659,6 @@ def read_spin_ham_from_file(fname):
518
659
  ham.set_dmi_ijR(parser.dmi)
519
660
  if parser.has_bilinear:
520
661
  ham.set_bilinear_ijR(parser.bilinear)
662
+ if parser.has_sia_tensor:
663
+ ham.set_sia_tensor(parser.sia_tensor)
521
664
  return ham