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
TB2J/io_exchange/edit.py
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TB2J_edit: A library for modifying TB2J results.
|
|
3
|
+
|
|
4
|
+
This library provides a simple interface for editing TB2J exchange parameters,
|
|
5
|
+
including single ion anisotropy, DMI, anisotropic exchange, and symmetry operations.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from TB2J.io_exchange.edit import load, set_anisotropy, toggle_DMI, save
|
|
9
|
+
>>> spinio = load('TB2J_results/TB2J.pickle')
|
|
10
|
+
>>> set_anisotropy(spinio, species='Sm', k1=5.0, k1dir=[0, 0, 1])
|
|
11
|
+
>>> toggle_DMI(spinio, enabled=False)
|
|
12
|
+
>>> save(spinio, 'modified_results')
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from TB2J.io_exchange.io_exchange import SpinIO
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"load",
|
|
23
|
+
"save",
|
|
24
|
+
"set_anisotropy",
|
|
25
|
+
"set_sia_tensor",
|
|
26
|
+
"remove_sia_tensor",
|
|
27
|
+
"toggle_DMI",
|
|
28
|
+
"toggle_Jani",
|
|
29
|
+
"toggle_exchange",
|
|
30
|
+
"remove_sublattice",
|
|
31
|
+
"symmetrize_exchange",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load(path="TB2J_results/TB2J.pickle"):
|
|
36
|
+
"""
|
|
37
|
+
Load TB2J results from a pickle file.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
path : str, optional
|
|
42
|
+
Path to the pickle file. Default is 'TB2J_results/TB2J.pickle'.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
spinio : SpinIO
|
|
47
|
+
The loaded SpinIO object containing all TB2J results.
|
|
48
|
+
|
|
49
|
+
Raises
|
|
50
|
+
------
|
|
51
|
+
FileNotFoundError
|
|
52
|
+
If the specified pickle file does not exist.
|
|
53
|
+
|
|
54
|
+
Examples
|
|
55
|
+
--------
|
|
56
|
+
>>> spinio = load('my_results/TB2J.pickle')
|
|
57
|
+
>>> print(spinio.exchange_Jdict)
|
|
58
|
+
"""
|
|
59
|
+
path = os.path.abspath(path)
|
|
60
|
+
if os.path.isdir(path):
|
|
61
|
+
path = os.path.join(path, "TB2J.pickle")
|
|
62
|
+
if not os.path.exists(path):
|
|
63
|
+
raise FileNotFoundError(f"TB2J pickle file not found: {path}")
|
|
64
|
+
|
|
65
|
+
dirname, fname = os.path.split(path)
|
|
66
|
+
return SpinIO.load_pickle(path=dirname, fname=fname)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def save(spinio, path="modified_results"):
|
|
70
|
+
"""
|
|
71
|
+
Save modified TB2J results to disk in all supported formats.
|
|
72
|
+
|
|
73
|
+
This writes the pickle file, TXT format, Multibinit XML, and other formats.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
spinio : SpinIO
|
|
78
|
+
The SpinIO object to save.
|
|
79
|
+
path : str, optional
|
|
80
|
+
Output directory path. Default is 'modified_results'.
|
|
81
|
+
Will be created if it doesn't exist.
|
|
82
|
+
|
|
83
|
+
Examples
|
|
84
|
+
--------
|
|
85
|
+
>>> save(spinio, 'my_modified_results')
|
|
86
|
+
"""
|
|
87
|
+
spinio.write_all(path=path)
|
|
88
|
+
|
|
89
|
+
# Fix mb.in to use SIA from exchange.xml when SIA is present
|
|
90
|
+
if spinio.has_uniaxial_anistropy or (
|
|
91
|
+
hasattr(spinio, "has_sia_tensor") and spinio.has_sia_tensor
|
|
92
|
+
):
|
|
93
|
+
mb_in_path = os.path.join(path, "Multibinit", "mb.in")
|
|
94
|
+
if os.path.exists(mb_in_path):
|
|
95
|
+
with open(mb_in_path, "r") as f:
|
|
96
|
+
content = f.read()
|
|
97
|
+
# Replace spin_sia_add = 1 with spin_sia_add = 0
|
|
98
|
+
content = content.replace("spin_sia_add = 1", "spin_sia_add = 0")
|
|
99
|
+
with open(mb_in_path, "w") as f:
|
|
100
|
+
f.write(content)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def set_anisotropy(spinio, species, k1=None, k1dir=None):
|
|
104
|
+
"""
|
|
105
|
+
Set single ion anisotropy for all atoms of a specified species.
|
|
106
|
+
|
|
107
|
+
Modifies the k1 (amplitude) and/or k1dir (direction) for all magnetic
|
|
108
|
+
atoms of the given chemical species.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
spinio : SpinIO
|
|
113
|
+
The SpinIO object to modify.
|
|
114
|
+
species : str
|
|
115
|
+
Chemical species symbol (e.g., 'Sm', 'Fe').
|
|
116
|
+
k1 : float, optional
|
|
117
|
+
Anisotropy amplitude in eV. If None, only k1dir is modified.
|
|
118
|
+
k1dir : array-like, optional
|
|
119
|
+
Anisotropy direction as a 3D vector [x, y, z].
|
|
120
|
+
Will be normalized automatically. If None, only k1 is modified.
|
|
121
|
+
|
|
122
|
+
Notes
|
|
123
|
+
-----
|
|
124
|
+
- Only magnetic atoms (those with index_spin >= 0) are modified.
|
|
125
|
+
- If k1/k1dir are None in the SpinIO object, they will be initialized.
|
|
126
|
+
- k1dir is always normalized to a unit vector.
|
|
127
|
+
|
|
128
|
+
Examples
|
|
129
|
+
--------
|
|
130
|
+
>>> # Set Sm anisotropy to 5 meV along z-axis
|
|
131
|
+
>>> set_anisotropy(spinio, species='Sm', k1=0.005, k1dir=[0, 0, 1])
|
|
132
|
+
|
|
133
|
+
>>> # Set only the direction for Fe
|
|
134
|
+
>>> set_anisotropy(spinio, species='Fe', k1dir=[1, 0, 0])
|
|
135
|
+
"""
|
|
136
|
+
# Get symbols and find target atoms
|
|
137
|
+
symbols = spinio.atoms.get_chemical_symbols()
|
|
138
|
+
|
|
139
|
+
target_indices = [
|
|
140
|
+
i
|
|
141
|
+
for i, (sym, idx) in enumerate(zip(symbols, spinio.index_spin))
|
|
142
|
+
if sym == species and idx >= 0
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
if not target_indices:
|
|
146
|
+
import warnings
|
|
147
|
+
|
|
148
|
+
warnings.warn(
|
|
149
|
+
f"No magnetic atoms found for species '{species}'. "
|
|
150
|
+
f"Either the species doesn't exist or has no magnetic atoms.",
|
|
151
|
+
UserWarning,
|
|
152
|
+
)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Initialize k1/k1dir if not present
|
|
156
|
+
n_spins = (
|
|
157
|
+
max(spinio.index_spin) + 1
|
|
158
|
+
if max(spinio.index_spin) >= 0
|
|
159
|
+
else len(spinio.index_spin)
|
|
160
|
+
)
|
|
161
|
+
if spinio.k1 is None:
|
|
162
|
+
spinio.k1 = [0.0] * n_spins
|
|
163
|
+
if spinio.k1dir is None:
|
|
164
|
+
spinio.k1dir = [[0.0, 0.0, 1.0]] * n_spins
|
|
165
|
+
|
|
166
|
+
# Set values for matching atoms
|
|
167
|
+
for iatom in target_indices:
|
|
168
|
+
ispin = spinio.index_spin[iatom]
|
|
169
|
+
if k1 is not None:
|
|
170
|
+
spinio.k1[ispin] = k1 # eV
|
|
171
|
+
if k1dir is not None:
|
|
172
|
+
k1dir_array = np.array(k1dir, dtype=float)
|
|
173
|
+
norm = np.linalg.norm(k1dir_array)
|
|
174
|
+
if norm == 0:
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"k1dir cannot be a zero vector for species '{species}'"
|
|
177
|
+
)
|
|
178
|
+
spinio.k1dir[ispin] = k1dir_array / norm
|
|
179
|
+
|
|
180
|
+
# Set the flag to enable SIA in output
|
|
181
|
+
spinio.has_uniaxial_anistropy = True
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def set_sia_tensor(spinio, species, tensor):
|
|
185
|
+
"""
|
|
186
|
+
Set full single ion anisotropy tensor for all atoms of a specified species.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
spinio : SpinIO
|
|
191
|
+
The SpinIO object to modify.
|
|
192
|
+
species : str
|
|
193
|
+
Chemical species symbol (e.g., 'Sm', 'Fe').
|
|
194
|
+
tensor : array-like
|
|
195
|
+
3x3 anisotropy tensor in eV.
|
|
196
|
+
|
|
197
|
+
Examples
|
|
198
|
+
--------
|
|
199
|
+
>>> # Set Sm anisotropy tensor
|
|
200
|
+
>>> tensor = np.diag([0.001, 0.002, 0.003])
|
|
201
|
+
>>> set_sia_tensor(spinio, species='Sm', tensor=tensor)
|
|
202
|
+
"""
|
|
203
|
+
# Get symbols and find target atoms
|
|
204
|
+
symbols = spinio.atoms.get_chemical_symbols()
|
|
205
|
+
|
|
206
|
+
target_indices = [
|
|
207
|
+
i
|
|
208
|
+
for i, (sym, idx) in enumerate(zip(symbols, spinio.index_spin))
|
|
209
|
+
if sym == species and idx >= 0
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
if not target_indices:
|
|
213
|
+
import warnings
|
|
214
|
+
|
|
215
|
+
warnings.warn(
|
|
216
|
+
f"No magnetic atoms found for species '{species}'. "
|
|
217
|
+
f"Either the species doesn't exist or has no magnetic atoms.",
|
|
218
|
+
UserWarning,
|
|
219
|
+
)
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
# Initialize sia_tensor dict if not present
|
|
223
|
+
if not hasattr(spinio, "sia_tensor") or spinio.sia_tensor is None:
|
|
224
|
+
spinio.sia_tensor = {}
|
|
225
|
+
|
|
226
|
+
tensor_array = np.array(tensor, dtype=float)
|
|
227
|
+
if tensor_array.shape != (3, 3):
|
|
228
|
+
raise ValueError("SIA tensor must be a 3x3 matrix")
|
|
229
|
+
|
|
230
|
+
# Set values for matching atoms
|
|
231
|
+
for iatom in target_indices:
|
|
232
|
+
ispin = spinio.index_spin[iatom]
|
|
233
|
+
spinio.sia_tensor[ispin] = tensor_array.copy()
|
|
234
|
+
|
|
235
|
+
# Set the flag to enable SIA tensor in output
|
|
236
|
+
spinio.has_sia_tensor = True
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def remove_sia_tensor(spinio, species=None):
|
|
240
|
+
"""
|
|
241
|
+
Remove single ion anisotropy tensor.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
spinio : SpinIO
|
|
246
|
+
The SpinIO object to modify.
|
|
247
|
+
species : str, optional
|
|
248
|
+
Chemical species symbol (e.g., 'Sm', 'Fe').
|
|
249
|
+
If None, remove for all atoms.
|
|
250
|
+
"""
|
|
251
|
+
if not hasattr(spinio, "sia_tensor") or spinio.sia_tensor is None:
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
if species is None:
|
|
255
|
+
spinio.sia_tensor = None
|
|
256
|
+
spinio.has_sia_tensor = False
|
|
257
|
+
else:
|
|
258
|
+
symbols = spinio.atoms.get_chemical_symbols()
|
|
259
|
+
target_indices = [
|
|
260
|
+
i
|
|
261
|
+
for i, (sym, idx) in enumerate(zip(symbols, spinio.index_spin))
|
|
262
|
+
if sym == species and idx >= 0
|
|
263
|
+
]
|
|
264
|
+
for iatom in target_indices:
|
|
265
|
+
ispin = spinio.index_spin[iatom]
|
|
266
|
+
if ispin in spinio.sia_tensor:
|
|
267
|
+
del spinio.sia_tensor[ispin]
|
|
268
|
+
if not spinio.sia_tensor:
|
|
269
|
+
spinio.has_sia_tensor = False
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def toggle_DMI(spinio, enabled=None):
|
|
273
|
+
"""
|
|
274
|
+
Enable or disable Dzyaloshinskii-Moriya interactions (DMI).
|
|
275
|
+
|
|
276
|
+
When disabling, the DMI values are backed up and can be restored
|
|
277
|
+
by re-enabling.
|
|
278
|
+
|
|
279
|
+
Parameters
|
|
280
|
+
----------
|
|
281
|
+
spinio : SpinIO
|
|
282
|
+
The SpinIO object to modify.
|
|
283
|
+
enabled : bool, optional
|
|
284
|
+
If True, enable DMI. If False, disable DMI.
|
|
285
|
+
If None, toggle the current state.
|
|
286
|
+
|
|
287
|
+
Examples
|
|
288
|
+
--------
|
|
289
|
+
>>> # Disable DMI
|
|
290
|
+
>>> toggle_DMI(spinio, enabled=False)
|
|
291
|
+
|
|
292
|
+
>>> # Toggle DMI (disable if enabled, enable if disabled)
|
|
293
|
+
>>> toggle_DMI(spinio)
|
|
294
|
+
|
|
295
|
+
>>> # Re-enable DMI
|
|
296
|
+
>>> toggle_DMI(spinio, enabled=True)
|
|
297
|
+
"""
|
|
298
|
+
if enabled is None:
|
|
299
|
+
# Toggle current state
|
|
300
|
+
enabled = not spinio.has_dmi
|
|
301
|
+
|
|
302
|
+
if enabled and not spinio.has_dmi:
|
|
303
|
+
# Re-enable: restore from backup or initialize empty
|
|
304
|
+
if hasattr(spinio, "_dmi_backup"):
|
|
305
|
+
spinio.dmi_ddict = spinio._dmi_backup
|
|
306
|
+
else:
|
|
307
|
+
spinio.dmi_ddict = {}
|
|
308
|
+
elif not enabled and spinio.has_dmi:
|
|
309
|
+
# Disable: backup and clear
|
|
310
|
+
spinio._dmi_backup = spinio.dmi_ddict
|
|
311
|
+
spinio.dmi_ddict = None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def toggle_Jani(spinio, enabled=None):
|
|
315
|
+
"""
|
|
316
|
+
Enable or disable symmetric anisotropic exchange (Jani).
|
|
317
|
+
|
|
318
|
+
When disabling, the Jani values are backed up and can be restored
|
|
319
|
+
by re-enabling.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
spinio : SpinIO
|
|
324
|
+
The SpinIO object to modify.
|
|
325
|
+
enabled : bool, optional
|
|
326
|
+
If True, enable Jani. If False, disable Jani.
|
|
327
|
+
If None, toggle the current state.
|
|
328
|
+
|
|
329
|
+
Examples
|
|
330
|
+
--------
|
|
331
|
+
>>> # Disable anisotropic exchange
|
|
332
|
+
>>> toggle_Jani(spinio, enabled=False)
|
|
333
|
+
|
|
334
|
+
>>> # Toggle anisotropic exchange
|
|
335
|
+
>>> toggle_Jani(spinio)
|
|
336
|
+
|
|
337
|
+
>>> # Re-enable anisotropic exchange
|
|
338
|
+
>>> toggle_Jani(spinio, enabled=True)
|
|
339
|
+
"""
|
|
340
|
+
if enabled is None:
|
|
341
|
+
# Toggle current state
|
|
342
|
+
enabled = not spinio.has_bilinear
|
|
343
|
+
|
|
344
|
+
if enabled and not spinio.has_bilinear:
|
|
345
|
+
# Re-enable: restore from backup or initialize empty
|
|
346
|
+
if hasattr(spinio, "_jani_backup"):
|
|
347
|
+
spinio.Jani_dict = spinio._jani_backup
|
|
348
|
+
else:
|
|
349
|
+
spinio.Jani_dict = {}
|
|
350
|
+
elif not enabled and spinio.has_bilinear:
|
|
351
|
+
# Disable: backup and clear
|
|
352
|
+
spinio._jani_backup = spinio.Jani_dict
|
|
353
|
+
spinio.Jani_dict = None
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def toggle_exchange(spinio, enabled=None):
|
|
357
|
+
"""
|
|
358
|
+
Enable or disable isotropic exchange parameters.
|
|
359
|
+
|
|
360
|
+
When disabling, the exchange values are backed up and can be restored
|
|
361
|
+
by re-enabling.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
spinio : SpinIO
|
|
366
|
+
The SpinIO object to modify.
|
|
367
|
+
enabled : bool, optional
|
|
368
|
+
If True, enable exchange. If False, disable exchange.
|
|
369
|
+
If None, toggle the current state.
|
|
370
|
+
|
|
371
|
+
Examples
|
|
372
|
+
--------
|
|
373
|
+
>>> # Disable isotropic exchange
|
|
374
|
+
>>> toggle_exchange(spinio, enabled=False)
|
|
375
|
+
|
|
376
|
+
>>> # Toggle isotropic exchange
|
|
377
|
+
>>> toggle_exchange(spinio)
|
|
378
|
+
|
|
379
|
+
>>> # Re-enable isotropic exchange
|
|
380
|
+
>>> toggle_exchange(spinio, enabled=True)
|
|
381
|
+
"""
|
|
382
|
+
if enabled is None:
|
|
383
|
+
# Toggle current state
|
|
384
|
+
enabled = not spinio.has_exchange
|
|
385
|
+
|
|
386
|
+
if enabled and not spinio.has_exchange:
|
|
387
|
+
# Re-enable: restore from backup or initialize empty
|
|
388
|
+
if hasattr(spinio, "_exchange_backup"):
|
|
389
|
+
spinio.exchange_Jdict = spinio._exchange_backup
|
|
390
|
+
else:
|
|
391
|
+
spinio.exchange_Jdict = {}
|
|
392
|
+
elif not enabled and spinio.has_exchange:
|
|
393
|
+
# Disable: backup and clear
|
|
394
|
+
spinio._exchange_backup = spinio.exchange_Jdict
|
|
395
|
+
spinio.exchange_Jdict = None
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def remove_sublattice(spinio, sublattice_name):
|
|
399
|
+
"""
|
|
400
|
+
Remove all magnetic interactions associated with a specific sublattice.
|
|
401
|
+
|
|
402
|
+
This includes:
|
|
403
|
+
- Single-ion anisotropy (SIA) for atoms in the sublattice.
|
|
404
|
+
- Exchange interactions (J) where i or j belongs to the sublattice.
|
|
405
|
+
- Dzyaloshinskii-Moriya interactions (DMI) where i or j belongs to the sublattice.
|
|
406
|
+
- Anisotropic exchange where i or j belongs to the sublattice.
|
|
407
|
+
|
|
408
|
+
Parameters
|
|
409
|
+
----------
|
|
410
|
+
spinio : SpinIO
|
|
411
|
+
The SpinIO object to modify.
|
|
412
|
+
sublattice_name : str
|
|
413
|
+
The name of the sublattice (species symbol) to remove.
|
|
414
|
+
|
|
415
|
+
Examples
|
|
416
|
+
--------
|
|
417
|
+
>>> # Remove all interactions involving Sm atoms
|
|
418
|
+
>>> remove_sublattice(spinio, 'Sm')
|
|
419
|
+
"""
|
|
420
|
+
symbols = spinio.atoms.get_chemical_symbols()
|
|
421
|
+
sublattice_indices = [
|
|
422
|
+
i
|
|
423
|
+
for i, (sym, idx) in enumerate(zip(symbols, spinio.index_spin))
|
|
424
|
+
if sym == sublattice_name and idx >= 0
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
if not sublattice_indices:
|
|
428
|
+
import warnings
|
|
429
|
+
|
|
430
|
+
warnings.warn(f"No magnetic atoms found for sublattice '{sublattice_name}'.")
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
sublattice_spin_indices = set(spinio.index_spin[i] for i in sublattice_indices)
|
|
434
|
+
|
|
435
|
+
for i in sublattice_indices:
|
|
436
|
+
spinio.index_spin[i] = -1
|
|
437
|
+
|
|
438
|
+
# Re-index spins
|
|
439
|
+
# Map old spin indices to new spin indices
|
|
440
|
+
# Remaining spins will be compacted to 0, 1, 2...
|
|
441
|
+
|
|
442
|
+
old_to_new_spin_index = {}
|
|
443
|
+
current_spin_index = 0
|
|
444
|
+
# max_old_spin = max(spinio.index_spin)
|
|
445
|
+
|
|
446
|
+
for iatom, ispin in enumerate(spinio.index_spin):
|
|
447
|
+
if ispin >= 0:
|
|
448
|
+
if ispin not in old_to_new_spin_index:
|
|
449
|
+
old_to_new_spin_index[ispin] = current_spin_index
|
|
450
|
+
current_spin_index += 1
|
|
451
|
+
spinio.index_spin[iatom] = old_to_new_spin_index[ispin]
|
|
452
|
+
|
|
453
|
+
def reindex_interaction(interaction_dict):
|
|
454
|
+
if interaction_dict is None:
|
|
455
|
+
return None
|
|
456
|
+
new_dict = {}
|
|
457
|
+
for key, val in interaction_dict.items():
|
|
458
|
+
R, i, j = key
|
|
459
|
+
if i in old_to_new_spin_index and j in old_to_new_spin_index:
|
|
460
|
+
new_i = old_to_new_spin_index[i]
|
|
461
|
+
new_j = old_to_new_spin_index[j]
|
|
462
|
+
new_dict[(R, new_i, new_j)] = val
|
|
463
|
+
return new_dict
|
|
464
|
+
|
|
465
|
+
def filter_interaction(interaction_dict):
|
|
466
|
+
if interaction_dict is None:
|
|
467
|
+
return None
|
|
468
|
+
new_dict = {}
|
|
469
|
+
for key, val in interaction_dict.items():
|
|
470
|
+
R, i, j = key
|
|
471
|
+
if i not in sublattice_spin_indices and j not in sublattice_spin_indices:
|
|
472
|
+
new_dict[key] = val
|
|
473
|
+
return new_dict
|
|
474
|
+
|
|
475
|
+
def filter_distance_dict(distance_dict):
|
|
476
|
+
if distance_dict is None:
|
|
477
|
+
return None
|
|
478
|
+
new_dict = {}
|
|
479
|
+
for key, val in distance_dict.items():
|
|
480
|
+
R, i, j = key
|
|
481
|
+
if i not in sublattice_spin_indices and j not in sublattice_spin_indices:
|
|
482
|
+
new_dict[key] = val
|
|
483
|
+
return new_dict
|
|
484
|
+
|
|
485
|
+
if spinio.has_exchange:
|
|
486
|
+
spinio.exchange_Jdict = filter_interaction(spinio.exchange_Jdict)
|
|
487
|
+
spinio.exchange_Jdict = reindex_interaction(spinio.exchange_Jdict)
|
|
488
|
+
spinio.distance_dict = filter_distance_dict(spinio.distance_dict)
|
|
489
|
+
spinio.distance_dict = reindex_interaction(spinio.distance_dict)
|
|
490
|
+
if hasattr(spinio, "Jiso_orb") and spinio.Jiso_orb:
|
|
491
|
+
spinio.Jiso_orb = filter_interaction(spinio.Jiso_orb)
|
|
492
|
+
spinio.Jiso_orb = reindex_interaction(spinio.Jiso_orb)
|
|
493
|
+
|
|
494
|
+
if spinio.has_dmi:
|
|
495
|
+
spinio.dmi_ddict = filter_interaction(spinio.dmi_ddict)
|
|
496
|
+
spinio.dmi_ddict = reindex_interaction(spinio.dmi_ddict)
|
|
497
|
+
if hasattr(spinio, "DMI_orb") and spinio.DMI_orb:
|
|
498
|
+
spinio.DMI_orb = filter_interaction(spinio.DMI_orb)
|
|
499
|
+
spinio.DMI_orb = reindex_interaction(spinio.DMI_orb)
|
|
500
|
+
|
|
501
|
+
if spinio.has_bilinear:
|
|
502
|
+
spinio.Jani_dict = filter_interaction(spinio.Jani_dict)
|
|
503
|
+
spinio.Jani_dict = reindex_interaction(spinio.Jani_dict)
|
|
504
|
+
if hasattr(spinio, "Jani_orb") and spinio.Jani_orb:
|
|
505
|
+
spinio.Jani_orb = filter_interaction(spinio.Jani_orb)
|
|
506
|
+
spinio.Jani_orb = reindex_interaction(spinio.Jani_orb)
|
|
507
|
+
|
|
508
|
+
if hasattr(spinio, "dJdx") and spinio.dJdx:
|
|
509
|
+
spinio.dJdx = filter_distance_dict(spinio.dJdx)
|
|
510
|
+
spinio.dJdx = reindex_interaction(spinio.dJdx)
|
|
511
|
+
if hasattr(spinio, "dJdx2") and spinio.dJdx2:
|
|
512
|
+
spinio.dJdx2 = filter_distance_dict(spinio.dJdx2)
|
|
513
|
+
spinio.dJdx2 = reindex_interaction(spinio.dJdx2)
|
|
514
|
+
if hasattr(spinio, "biquadratic_Jdict") and spinio.biquadratic_Jdict:
|
|
515
|
+
spinio.biquadratic_Jdict = filter_interaction(spinio.biquadratic_Jdict)
|
|
516
|
+
spinio.biquadratic_Jdict = reindex_interaction(spinio.biquadratic_Jdict)
|
|
517
|
+
if hasattr(spinio, "NJT_Jdict") and spinio.NJT_Jdict:
|
|
518
|
+
spinio.NJT_Jdict = filter_interaction(spinio.NJT_Jdict)
|
|
519
|
+
spinio.NJT_Jdict = reindex_interaction(spinio.NJT_Jdict)
|
|
520
|
+
if hasattr(spinio, "NJT_ddict") and spinio.NJT_ddict:
|
|
521
|
+
spinio.NJT_ddict = filter_interaction(spinio.NJT_ddict)
|
|
522
|
+
spinio.NJT_ddict = reindex_interaction(spinio.NJT_ddict)
|
|
523
|
+
|
|
524
|
+
if spinio.has_uniaxial_anistropy:
|
|
525
|
+
if spinio.k1 is not None:
|
|
526
|
+
new_k1 = [0.0] * current_spin_index
|
|
527
|
+
new_k1dir = [[0.0, 0.0, 1.0]] * current_spin_index
|
|
528
|
+
|
|
529
|
+
for old_idx, new_idx in old_to_new_spin_index.items():
|
|
530
|
+
if old_idx < len(spinio.k1):
|
|
531
|
+
new_k1[new_idx] = spinio.k1[old_idx]
|
|
532
|
+
new_k1dir[new_idx] = spinio.k1dir[old_idx]
|
|
533
|
+
|
|
534
|
+
spinio.k1 = new_k1
|
|
535
|
+
spinio.k1dir = new_k1dir
|
|
536
|
+
|
|
537
|
+
if (
|
|
538
|
+
hasattr(spinio, "has_sia_tensor")
|
|
539
|
+
and spinio.has_sia_tensor
|
|
540
|
+
and spinio.sia_tensor is not None
|
|
541
|
+
):
|
|
542
|
+
new_sia_tensor = {}
|
|
543
|
+
for old_idx, tensor in spinio.sia_tensor.items():
|
|
544
|
+
if old_idx in old_to_new_spin_index:
|
|
545
|
+
new_idx = old_to_new_spin_index[old_idx]
|
|
546
|
+
new_sia_tensor[new_idx] = tensor
|
|
547
|
+
spinio.sia_tensor = new_sia_tensor
|
|
548
|
+
# If no tensors remain, set has_sia_tensor to False
|
|
549
|
+
if not spinio.sia_tensor:
|
|
550
|
+
spinio.has_sia_tensor = False
|
|
551
|
+
|
|
552
|
+
spinio._ind_atoms = {}
|
|
553
|
+
for iatom, ispin in enumerate(spinio.index_spin):
|
|
554
|
+
if ispin >= 0:
|
|
555
|
+
spinio._ind_atoms[ispin] = iatom
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def symmetrize_exchange(spinio, atoms, symprec=1e-3):
|
|
559
|
+
"""
|
|
560
|
+
Symmetrize isotropic exchange based on a provided atomic structure.
|
|
561
|
+
|
|
562
|
+
The symmetry is detected from the provided atomic structure using spglib.
|
|
563
|
+
Exchange parameters for symmetry-equivalent atom pairs are averaged.
|
|
564
|
+
|
|
565
|
+
Parameters
|
|
566
|
+
----------
|
|
567
|
+
spinio : SpinIO
|
|
568
|
+
The SpinIO object to modify.
|
|
569
|
+
atoms : ase.Atoms
|
|
570
|
+
Atomic structure that defines the target symmetry.
|
|
571
|
+
For example, provide a cubic structure to symmetrize to cubic symmetry.
|
|
572
|
+
symprec : float, optional
|
|
573
|
+
Symmetry precision in Angstrom. Default is 1e-3.
|
|
574
|
+
|
|
575
|
+
Notes
|
|
576
|
+
-----
|
|
577
|
+
- Only isotropic exchange (exchange_Jdict) is modified.
|
|
578
|
+
- DMI and anisotropic exchange are unchanged.
|
|
579
|
+
- The spinio.atoms structure is NOT modified; only exchange values change.
|
|
580
|
+
- Atoms are mapped between input and SpinIO structures by species and position.
|
|
581
|
+
|
|
582
|
+
Examples
|
|
583
|
+
--------
|
|
584
|
+
>>> from ase.io import read
|
|
585
|
+
>>> # Symmetrize to cubic symmetry
|
|
586
|
+
>>> cubic_structure = read('cubic_smfeo3.cif')
|
|
587
|
+
>>> symmetrize_exchange(spinio, atoms=cubic_structure)
|
|
588
|
+
|
|
589
|
+
>>> # Symmetrize to original Pnma symmetry (averaging within groups)
|
|
590
|
+
>>> symmetrize_exchange(spinio, atoms=spinio.atoms)
|
|
591
|
+
"""
|
|
592
|
+
from TB2J.symmetrize_J import symmetrize_exchange as sym_exchange
|
|
593
|
+
|
|
594
|
+
sym_exchange(spinio, atoms, symprec)
|