TB2J 0.9.12.9__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.
- TB2J/MAE.py +8 -1
- TB2J/MAEGreen.py +78 -61
- TB2J/contour.py +3 -2
- TB2J/exchange.py +346 -51
- TB2J/exchangeCL2.py +285 -47
- TB2J/exchange_params.py +48 -0
- TB2J/green.py +73 -36
- TB2J/interfaces/abacus/gen_exchange_abacus.py +2 -1
- TB2J/interfaces/wannier90_interface.py +4 -4
- TB2J/io_exchange/__init__.py +19 -1
- TB2J/io_exchange/edit.py +594 -0
- TB2J/io_exchange/io_espins.py +276 -0
- TB2J/io_exchange/io_exchange.py +248 -76
- TB2J/io_exchange/io_tomsasd.py +4 -3
- TB2J/io_exchange/io_txt.py +72 -7
- TB2J/io_exchange/io_vampire.py +4 -2
- TB2J/io_merge.py +60 -40
- TB2J/magnon/magnon3.py +27 -2
- TB2J/mathutils/rotate_spin.py +7 -7
- TB2J/myTB.py +11 -11
- TB2J/mycfr.py +11 -11
- TB2J/pauli.py +32 -2
- TB2J/plot.py +26 -0
- TB2J/rotate_atoms.py +9 -6
- TB2J/scripts/TB2J_edit.py +403 -0
- TB2J/scripts/TB2J_plot_exchange.py +48 -0
- TB2J/spinham/hamiltonian.py +156 -13
- TB2J/spinham/hamiltonian_terms.py +40 -1
- TB2J/spinham/spin_xml.py +40 -8
- TB2J/symmetrize_J.py +140 -9
- TB2J/tests/test_cli_remove_sublattice.py +33 -0
- TB2J/tests/test_cli_toggle_exchange.py +50 -0
- {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/METADATA +10 -7
- {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/RECORD +38 -34
- {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/WHEEL +1 -1
- {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/entry_points.txt +2 -0
- TB2J/interfaces/abacus/test_read_HRSR.py +0 -43
- TB2J/interfaces/abacus/test_read_stru.py +0 -32
- {tb2j-0.9.12.9.dist-info → tb2j-0.9.12.22.dist-info}/licenses/LICENSE +0 -0
- {tb2j-0.9.12.9.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()
|
TB2J/spinham/hamiltonian.py
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
|
-
import
|
|
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
|
|
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
|