TB2J 0.9.4rc0__py3-none-any.whl → 0.9.6rc0__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 +108 -24
- TB2J/anisotropy.py +672 -0
- TB2J/contour.py +8 -0
- TB2J/exchange.py +43 -103
- TB2J/exchangeCL2.py +11 -13
- TB2J/exchange_params.py +213 -0
- TB2J/green.py +62 -27
- TB2J/interfaces/__init__.py +12 -0
- TB2J/interfaces/abacus/__init__.py +4 -0
- TB2J/{abacus → interfaces/abacus}/abacus_api.py +3 -3
- TB2J/{abacus → interfaces/abacus}/abacus_wrapper.py +11 -7
- TB2J/{abacus → interfaces/abacus}/gen_exchange_abacus.py +6 -3
- TB2J/{abacus → interfaces/abacus}/orbital_api.py +4 -4
- TB2J/{abacus → interfaces/abacus}/stru_api.py +11 -11
- TB2J/{abacus → interfaces/abacus}/test_read_HRSR.py +0 -1
- TB2J/{abacus → interfaces/abacus}/test_read_stru.py +5 -3
- TB2J/interfaces/gpaw_interface.py +54 -0
- TB2J/interfaces/lawaf_interface.py +129 -0
- TB2J/interfaces/manager.py +23 -0
- TB2J/interfaces/siesta_interface.py +174 -0
- TB2J/interfaces/wannier90_interface.py +115 -0
- TB2J/io_exchange/io_exchange.py +21 -7
- TB2J/io_merge.py +2 -1
- TB2J/mathutils/fermi.py +11 -6
- TB2J/mathutils/lowdin.py +12 -2
- TB2J/mathutils/rotate_spin.py +222 -4
- TB2J/pauli.py +38 -2
- TB2J/symmetrize_J.py +120 -0
- TB2J/utils.py +82 -1
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/abacus2J.py +5 -4
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/siesta2J.py +21 -4
- TB2J-0.9.6rc0.data/scripts/wann2J.py +96 -0
- {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/METADATA +4 -3
- {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/RECORD +46 -46
- {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/WHEEL +1 -1
- TB2J-0.9.6rc0.dist-info/entry_points.txt +3 -0
- TB2J/abacus/MAE.py +0 -320
- TB2J/abacus/__init__.py +0 -1
- TB2J/abacus/occupations.py +0 -278
- TB2J/cut_cell.py +0 -82
- TB2J/io_exchange/io_pickle.py +0 -0
- TB2J/manager.py +0 -441
- TB2J/mathutils.py +0 -12
- TB2J/patch.py +0 -50
- TB2J/spinham/h_matrix.py +0 -68
- TB2J/spinham/obtain_J.py +0 -79
- TB2J/supercell.py +0 -532
- TB2J-0.9.4rc0.data/scripts/wann2J.py +0 -194
- TB2J/{abacus → interfaces/abacus}/test_density_matrix.py +1 -1
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_downfold.py +0 -0
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_eigen.py +0 -0
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_magnon.py +0 -0
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_merge.py +0 -0
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_rotate.py +0 -0
- {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_rotateDM.py +0 -0
- {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/LICENSE +0 -0
- {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/top_level.txt +0 -0
TB2J/io_exchange/io_exchange.py
CHANGED
@@ -9,16 +9,18 @@ write not only xml output.
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
import os
|
12
|
+
import pickle
|
12
13
|
from collections.abc import Iterable
|
14
|
+
from datetime import datetime
|
15
|
+
|
16
|
+
import matplotlib.pyplot as plt
|
13
17
|
import numpy as np
|
14
|
-
|
15
|
-
import pickle
|
18
|
+
|
16
19
|
from TB2J import __version__
|
20
|
+
from TB2J.io_exchange.io_txt import write_Jq_info
|
17
21
|
from TB2J.Jtensor import combine_J_tensor
|
18
|
-
from
|
19
|
-
import matplotlib.pyplot as plt
|
22
|
+
from TB2J.kpoints import monkhorst_pack
|
20
23
|
from TB2J.spinham.spin_api import SpinModel
|
21
|
-
from TB2J.io_exchange.io_txt import write_Jq_info
|
22
24
|
from TB2J.utils import symbol_number
|
23
25
|
|
24
26
|
|
@@ -238,6 +240,18 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
|
|
238
240
|
def get_charge_iatom(self, iatom):
|
239
241
|
return self.charges[iatom]
|
240
242
|
|
243
|
+
def ijR_index_spin_to_atom(self, i, j, R):
|
244
|
+
return (self.iatom(i), self.iatom(j), R)
|
245
|
+
|
246
|
+
def ijR_index_atom_to_spin(self, iatom, jatom, R):
|
247
|
+
return (self.index_spin[iatom], self.index_spin[jatom], R)
|
248
|
+
|
249
|
+
def ijR_list(self):
|
250
|
+
return [(i, j, R) for R, i, j in self.exchange_Jdict]
|
251
|
+
|
252
|
+
def ijR_list_index_atom(self):
|
253
|
+
return [self.ijR_index_spin_to_atom(i, j, R) for R, i, j in self.exchange_Jdict]
|
254
|
+
|
241
255
|
def get_J(self, i, j, R, default=None):
|
242
256
|
i = self.i_spin(i)
|
243
257
|
j = self.i_spin(j)
|
@@ -380,6 +394,7 @@ Generation time: {now.strftime("%y/%m/%d %H:%M:%S")}
|
|
380
394
|
self.write_multibinit(path=os.path.join(path, "Multibinit"))
|
381
395
|
self.write_tom_format(path=os.path.join(path, "TomASD"))
|
382
396
|
self.write_vampire(path=os.path.join(path, "Vampire"))
|
397
|
+
|
383
398
|
self.plot_all(savefile=os.path.join(path, "JvsR.pdf"))
|
384
399
|
# self.write_Jq(kmesh=[9, 9, 9], path=path)
|
385
400
|
|
@@ -562,8 +577,8 @@ def gen_distance_dict(ind_mag_atoms, atoms, Rlist):
|
|
562
577
|
|
563
578
|
|
564
579
|
def test_spin_io():
|
565
|
-
from ase import Atoms
|
566
580
|
import numpy as np
|
581
|
+
from ase import Atoms
|
567
582
|
|
568
583
|
atoms = Atoms(
|
569
584
|
"SrMnO3",
|
@@ -579,7 +594,6 @@ def test_spin_io():
|
|
579
594
|
spinat = [[0, 0, x] for x in [0, 3, 0, 0, 0]]
|
580
595
|
charges = [2, 4, 5, 5, 5]
|
581
596
|
index_spin = [-1, 0, -1, -1, -1]
|
582
|
-
colinear = True
|
583
597
|
Rlist = [[0, 0, 0], [0, 0, 1]]
|
584
598
|
ind_mag_atoms = [1]
|
585
599
|
distance_dict = gen_distance_dict(ind_mag_atoms, atoms, Rlist)
|
TB2J/io_merge.py
CHANGED
@@ -48,7 +48,8 @@ class SpinIO_merge(SpinIO):
|
|
48
48
|
|
49
49
|
def _set_projection_vectors(self):
|
50
50
|
|
51
|
-
|
51
|
+
norm = np.linalg.norm(self.spinat, axis=-1).reshape(-1, 1)
|
52
|
+
spinat = self.spinat / norm
|
52
53
|
idx = [self.ind_atoms[i] for i in self.index_spin if i >= 0]
|
53
54
|
projv = {}
|
54
55
|
for i, j in combinations_with_replacement(range(self.nspin), 2):
|
TB2J/mathutils/fermi.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
import numpy as np
|
2
|
-
import warnings
|
3
1
|
import sys
|
4
2
|
|
5
|
-
|
3
|
+
import numpy as np
|
4
|
+
|
5
|
+
MAX_EXP_ARGUMENT = np.log(sys.float_info.max / 100000)
|
6
|
+
print(MAX_EXP_ARGUMENT)
|
6
7
|
|
7
8
|
|
8
9
|
def fermi(e, mu, width=0.01):
|
@@ -15,8 +16,12 @@ def fermi(e, mu, width=0.01):
|
|
15
16
|
"""
|
16
17
|
x = (e - mu) / width
|
17
18
|
# disable overflow warning
|
18
|
-
with warnings.catch_warnings():
|
19
|
-
|
20
|
-
|
19
|
+
# with warnings.catch_warnings():
|
20
|
+
# warnings.simplefilter("ignore")
|
21
|
+
# ret = np.where(x < MAX_EXP_ARGUMENT, 1 / (1.0 + np.exp(x)), 0.0)
|
21
22
|
|
23
|
+
ret = np.zeros_like(x, dtype=float)
|
24
|
+
for i, xi in enumerate(x):
|
25
|
+
if xi < 700:
|
26
|
+
ret[i] = 1 / (1.0 + np.exp(xi))
|
22
27
|
return ret
|
TB2J/mathutils/lowdin.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import numpy as np
|
2
|
-
from scipy.linalg import
|
2
|
+
from scipy.linalg import eigh
|
3
3
|
|
4
4
|
|
5
5
|
def Lowdin(S):
|
@@ -9,4 +9,14 @@ def Lowdin(S):
|
|
9
9
|
psi_prime = S^(-1/2) psi
|
10
10
|
"""
|
11
11
|
eigval, eigvec = eigh(S)
|
12
|
-
|
12
|
+
S_half = eigvec @ np.diag(np.sqrt(1.0 / eigval)) @ (eigvec.T.conj())
|
13
|
+
return S_half
|
14
|
+
|
15
|
+
|
16
|
+
def Lowdin_symmetric_orthonormalization(H, S):
|
17
|
+
"""
|
18
|
+
Lowdin's symmetric orthonormalization.
|
19
|
+
"""
|
20
|
+
S_half = Lowdin(S)
|
21
|
+
H_prime = S_half @ H @ S_half
|
22
|
+
return H_prime
|
TB2J/mathutils/rotate_spin.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import numpy as np
|
2
|
-
from
|
2
|
+
from scipy.sparse import eye_array, kron
|
3
|
+
|
4
|
+
from TB2J.pauli import gather_pauli_blocks, pauli_block_all
|
3
5
|
|
4
6
|
|
5
7
|
def rotate_Matrix_from_z_to_axis(M, axis, normalize=True):
|
@@ -27,12 +29,224 @@ def spherical_to_cartesian(theta, phi, normalize=True):
|
|
27
29
|
return vec
|
28
30
|
|
29
31
|
|
32
|
+
def rotation_matrix(theta, phi):
|
33
|
+
"""
|
34
|
+
The unitray operator U, that U^dagger * s3 * U is the rotated s3 by theta and phi
|
35
|
+
"""
|
36
|
+
U = np.array(
|
37
|
+
[
|
38
|
+
[np.cos(theta / 2), np.exp(-1j * phi) * np.sin(theta / 2)],
|
39
|
+
[-np.exp(1j * phi) * np.sin(theta / 2), np.cos(theta / 2)],
|
40
|
+
]
|
41
|
+
)
|
42
|
+
return U
|
43
|
+
|
44
|
+
|
45
|
+
def rotate_spinor_single_block(M, theta, phi):
|
46
|
+
"""
|
47
|
+
Rotate the spinor matrix M by theta and phi
|
48
|
+
"""
|
49
|
+
U = rotation_matrix(theta, phi)
|
50
|
+
Uinv = np.linalg.inv(U)
|
51
|
+
return Uinv @ M @ U
|
52
|
+
|
53
|
+
|
54
|
+
def rotate_spinor_matrix(M, theta, phi, method="einsum"):
|
55
|
+
"""
|
56
|
+
Rotate the spinor matrix M by theta and phi,
|
57
|
+
"""
|
58
|
+
if method == "plain":
|
59
|
+
return rotate_spinor_matrix_plain(M, theta, phi)
|
60
|
+
elif method == "einsum":
|
61
|
+
return rotate_spinor_matrix_einsum(M, theta, phi)
|
62
|
+
elif method == "reshape":
|
63
|
+
return rotate_spinor_matrix_reshape(M, theta, phi)
|
64
|
+
elif method == "kron":
|
65
|
+
return rotate_spinor_matrix_kron(M, theta, phi)
|
66
|
+
elif method == "spkron":
|
67
|
+
return rotate_spinor_matrix_spkron(M, theta, phi)
|
68
|
+
else:
|
69
|
+
raise ValueError(f"Unknown method: {method}")
|
70
|
+
|
71
|
+
|
72
|
+
def rotate_spinor_matrix_plain(M, theta, phi):
|
73
|
+
"""
|
74
|
+
M is a matrix with shape (2N, 2N), where N is the number of sites, and each site has a 2x2 matrix
|
75
|
+
rotate each 2x2 block by theta and phi
|
76
|
+
"""
|
77
|
+
Mnew = np.zeros_like(M)
|
78
|
+
U = rotation_matrix(theta, phi)
|
79
|
+
UT = U.conj().T
|
80
|
+
tmp = np.zeros((2, 2), dtype=np.complex128)
|
81
|
+
for i in range(M.shape[0] // 2):
|
82
|
+
for j in range(M.shape[0] // 2):
|
83
|
+
for k in range(2):
|
84
|
+
for l in range(2):
|
85
|
+
tmp[k, l] = M[2 * i + k, 2 * j + l]
|
86
|
+
# tmp[:, :]=M[2*i:2*i+2, 2*j:2*j+2]
|
87
|
+
Mnew[2 * i : 2 * i + 2, 2 * j : 2 * j + 2] = UT @ tmp @ U
|
88
|
+
return Mnew
|
89
|
+
|
90
|
+
|
91
|
+
def rotate_spinor_matrix_einsum(M, theta, phi):
|
92
|
+
"""
|
93
|
+
Rotate the spinor matrix M by theta and phi,
|
94
|
+
"""
|
95
|
+
N = M.shape[0] // 2
|
96
|
+
Mnew = np.reshape(M, (N, 2, N, 2)) # .swapaxes(1, 2)
|
97
|
+
# print("Mnew:", Mnew)
|
98
|
+
U = rotation_matrix(theta, phi)
|
99
|
+
UT = U.conj().T
|
100
|
+
Mnew = np.einsum(
|
101
|
+
"ij, rjsk, kl -> risl", UT, Mnew, U, optimize=True, dtype=np.complex128
|
102
|
+
)
|
103
|
+
Mnew = Mnew.reshape(2 * N, 2 * N)
|
104
|
+
return Mnew
|
105
|
+
|
106
|
+
|
107
|
+
def rotate_spinor_matrix_einsum_R(M, theta, phi):
|
108
|
+
"""
|
109
|
+
Rotate the spinor matrix M by theta and phi,
|
110
|
+
"""
|
111
|
+
nR = M.shape[0]
|
112
|
+
N = M.shape[1] // 2
|
113
|
+
Mnew = np.reshape(M, (nR, N, 2, N, 2)) # .swapaxes(1, 2)
|
114
|
+
# print("Mnew:", Mnew)
|
115
|
+
U = rotation_matrix(theta, phi)
|
116
|
+
UT = U.conj().T
|
117
|
+
Mnew = np.einsum(
|
118
|
+
"ij, nrjsk, kl -> nrisl", UT, Mnew, U, optimize=True, dtype=np.complex128
|
119
|
+
)
|
120
|
+
Mnew = Mnew.reshape(nR, 2 * N, 2 * N)
|
121
|
+
return Mnew
|
122
|
+
|
123
|
+
|
124
|
+
def rotate_spinor_Matrix_R(M, theta, phi):
|
125
|
+
return rotate_spinor_matrix_einsum_R(M, theta, phi)
|
126
|
+
|
127
|
+
|
128
|
+
def rotate_spinor_matrix_reshape(M, theta, phi):
|
129
|
+
"""
|
130
|
+
Rotate the spinor matrix M by theta and phi,
|
131
|
+
"""
|
132
|
+
N = M.shape[0] // 2
|
133
|
+
Mnew = np.reshape(M, (N, 2, N, 2)).swapaxes(1, 2)
|
134
|
+
# print("Mnew:", Mnew)
|
135
|
+
U = rotation_matrix(theta, phi)
|
136
|
+
UT = U.conj().T
|
137
|
+
Mnew = UT @ Mnew @ U
|
138
|
+
Mnew = Mnew.swapaxes(1, 2).reshape(2 * N, 2 * N)
|
139
|
+
return Mnew
|
140
|
+
|
141
|
+
|
142
|
+
def rotate_spinor_matrix_kron(M, theta, phi):
|
143
|
+
""" """
|
144
|
+
U = rotation_matrix(theta, phi)
|
145
|
+
# U = np.kron( U, np.eye(M.shape[0]//2))
|
146
|
+
# U = kron(eye_array(M.shape[0]//2), U)
|
147
|
+
U = np.kron(np.eye(M.shape[0] // 2), U)
|
148
|
+
M = U.conj().T @ M @ U
|
149
|
+
return M
|
150
|
+
|
151
|
+
|
152
|
+
def rotate_spinor_matrix_spkron(M, theta, phi):
|
153
|
+
""" """
|
154
|
+
U = rotation_matrix(theta, phi)
|
155
|
+
# U = np.kron( U, np.eye(M.shape[0]//2))
|
156
|
+
U = kron(eye_array(M.shape[0] // 2), U)
|
157
|
+
# U = np.kron(np.eye(M.shape[0]//2), U)
|
158
|
+
M = U.conj().T @ M @ U
|
159
|
+
return M
|
160
|
+
|
161
|
+
|
162
|
+
def test_rotate_spinor_M():
|
163
|
+
N = 2
|
164
|
+
M_re = np.random.rand(N * 2, N * 2)
|
165
|
+
M_im = np.random.rand(N * 2, N * 2)
|
166
|
+
M = M_re + 1j * M_im
|
167
|
+
M = M + M.T.conj()
|
168
|
+
# M=np.array([[1, 0], [0, 1]], dtype=np.complex128)
|
169
|
+
print(f"Original M: {M}")
|
170
|
+
|
171
|
+
import timeit
|
172
|
+
|
173
|
+
print("Time for rotate_spinor_matrix")
|
174
|
+
print(
|
175
|
+
timeit.timeit(lambda: rotate_spinor_matrix(M, np.pi / 2, np.pi / 2), number=10)
|
176
|
+
)
|
177
|
+
print("Time for rotate_spinor_matrix_einsum")
|
178
|
+
print(
|
179
|
+
timeit.timeit(
|
180
|
+
lambda: rotate_spinor_matrix_einsum(M, np.pi / 2, np.pi / 2), number=10
|
181
|
+
)
|
182
|
+
)
|
183
|
+
print("Time for rotate_spinor_matrix_reshape")
|
184
|
+
print(
|
185
|
+
timeit.timeit(
|
186
|
+
lambda: rotate_spinor_matrix_reshape(M, np.pi / 2, np.pi / 2), number=10
|
187
|
+
)
|
188
|
+
)
|
189
|
+
print("Time for rotate_spinor_matrix_kron")
|
190
|
+
print(
|
191
|
+
timeit.timeit(
|
192
|
+
lambda: rotate_spinor_matrix_kron(M, np.pi / 2, np.pi / 2), number=10
|
193
|
+
)
|
194
|
+
)
|
195
|
+
print("Time for rotate_spinor_matrix_spkron")
|
196
|
+
print(
|
197
|
+
timeit.timeit(
|
198
|
+
lambda: rotate_spinor_matrix_spkron(M, np.pi / 2, np.pi / 2), number=10
|
199
|
+
)
|
200
|
+
)
|
201
|
+
|
202
|
+
Mrot1 = rotate_spinor_matrix(M, np.pi / 2, np.pi / 2)
|
203
|
+
Mrot2 = rotate_spinor_matrix_einsum(M, np.pi / 2, np.pi / 2)
|
204
|
+
Mrot3 = rotate_spinor_matrix_reshape(M, np.pi / 2, np.pi / 2)
|
205
|
+
Mrot4 = rotate_spinor_matrix_kron(M, np.pi / 2, np.pi / 2)
|
206
|
+
Mrot5 = rotate_spinor_matrix_spkron(M, np.pi / 2, np.pi / 2)
|
207
|
+
print(f"Rotated M with jit:\n {Mrot1}")
|
208
|
+
print(f"Rotated M with einsum:\n {Mrot2-Mrot1}")
|
209
|
+
print(f"Rotated M with reshape:\n {Mrot3-Mrot1}")
|
210
|
+
print(f"Rotated M with kron:\n {Mrot4-Mrot1}")
|
211
|
+
print(f"Rotated M with spkron:\n {Mrot5-Mrot1}")
|
212
|
+
|
213
|
+
M_rot00 = rotate_spinor_matrix(M, 0, 0)
|
214
|
+
M_rot00_sph = rotate_Matrix_from_z_to_spherical(M, 0, 0)
|
215
|
+
print(f"Rotated M with theta=0, phi=0 compared with M:\n {M_rot00-M}")
|
216
|
+
print(f"Rotated M with theta=0, phi=0 compared with M:\n {M_rot00_sph-M}")
|
217
|
+
|
218
|
+
|
219
|
+
def test_rotate_spinor_oneblock():
|
220
|
+
M = np.array([[1.1, 0], [0, 0.9]])
|
221
|
+
print(np.array(pauli_block_all(M)).ravel())
|
222
|
+
print("Rotate by pi/2, pi/2 (z to y)")
|
223
|
+
Mnew = rotate_spinor_matrix_einsum(M, np.pi / 2, np.pi / 2)
|
224
|
+
print(np.array(pauli_block_all(Mnew)).ravel())
|
225
|
+
|
226
|
+
print("Rotate by pi/2, 0 (z to x)")
|
227
|
+
Mnew = rotate_spinor_matrix_kron(M, np.pi / 2, 0)
|
228
|
+
|
229
|
+
print(np.array(pauli_block_all(Mnew)).ravel())
|
230
|
+
|
231
|
+
print(Mnew)
|
232
|
+
|
233
|
+
|
30
234
|
def rotate_Matrix_from_z_to_spherical(M, theta, phi, normalize=True):
|
31
235
|
"""
|
32
236
|
Given a spinor matrix M, rotate it from z-axis to spherical coordinates
|
33
237
|
"""
|
34
|
-
axis = spherical_to_cartesian(theta, phi, normalize)
|
35
|
-
return rotate_Matrix_from_z_to_axis(M, axis, normalize)
|
238
|
+
# axis = spherical_to_cartesian(theta, phi, normalize)
|
239
|
+
# return rotate_Matrix_from_z_to_axis(M, axis, normalize)
|
240
|
+
return rotate_spinor_matrix_einsum(M, theta, phi)
|
241
|
+
|
242
|
+
|
243
|
+
def test_rotate_Matrix_from_z_to_spherical():
|
244
|
+
M_re = np.random.rand(2, 2)
|
245
|
+
M_im = np.random.rand(2, 2)
|
246
|
+
M = M_re + 1j * M_im
|
247
|
+
print(M)
|
248
|
+
M_rot = rotate_Matrix_from_z_to_spherical(M, 0, 0)
|
249
|
+
print(M_rot)
|
36
250
|
|
37
251
|
|
38
252
|
def test_rotate_Matrix_from_z_to_axis():
|
@@ -53,4 +267,8 @@ def test_rotate_Matrix_from_z_to_axis():
|
|
53
267
|
|
54
268
|
|
55
269
|
if __name__ == "__main__":
|
56
|
-
test_rotate_Matrix_from_z_to_axis()
|
270
|
+
# test_rotate_Matrix_from_z_to_axis()
|
271
|
+
# test_rotate_Matrix_from_z_to_spherical()
|
272
|
+
test_rotate_spinor_M()
|
273
|
+
# test_rotate_spinor_oneblock()
|
274
|
+
pass
|
TB2J/pauli.py
CHANGED
@@ -143,11 +143,47 @@ def pauli_block_all(M):
|
|
143
143
|
return MI, Mx, My, Mz
|
144
144
|
|
145
145
|
|
146
|
-
def gather_pauli_blocks(MI, Mx, My, Mz):
|
146
|
+
def gather_pauli_blocks(MI, Mx, My, Mz, coeffs=[1.0, 1.0, 1.0, 1.0]):
|
147
147
|
"""
|
148
148
|
Gather the I, x, y, z component of a matrix.
|
149
149
|
"""
|
150
|
-
|
150
|
+
cI, cx, cy, cz = coeffs
|
151
|
+
return (
|
152
|
+
cI * np.kron(MI, s0)
|
153
|
+
+ cx * np.kron(Mx, s1)
|
154
|
+
+ cy * np.kron(My, s2)
|
155
|
+
+ cz * np.kron(Mz, s3)
|
156
|
+
)
|
157
|
+
|
158
|
+
|
159
|
+
def pauli_part(M, coeffs=[1.0, 1.0, 1.0, 1.0]):
|
160
|
+
"""
|
161
|
+
Get the I, x, y, z part of a matrix.
|
162
|
+
"""
|
163
|
+
MI, Mx, My, Mz = pauli_block_all(M)
|
164
|
+
return gather_pauli_blocks(MI, Mx, My, Mz, coeffs=coeffs)
|
165
|
+
|
166
|
+
|
167
|
+
def chargepart(M):
|
168
|
+
"""
|
169
|
+
Get the charge part of a matrix.
|
170
|
+
"""
|
171
|
+
MI = (M[::2, ::2] + M[1::2, 1::2]) / 2.0
|
172
|
+
Mcopy = np.zeros_like(M)
|
173
|
+
Mcopy[::2, ::2] = MI
|
174
|
+
Mcopy[1::2, 1::2] = MI
|
175
|
+
return Mcopy
|
176
|
+
|
177
|
+
|
178
|
+
def spinpart(M):
|
179
|
+
"""
|
180
|
+
Get the spin part of a matrix.
|
181
|
+
"""
|
182
|
+
MI = (M[::2, ::2] + M[1::2, 1::2]) / 2.0
|
183
|
+
Mcopy = M.copy()
|
184
|
+
Mcopy[::2, ::2] -= MI
|
185
|
+
Mcopy[1::2, 1::2] -= MI
|
186
|
+
return Mcopy
|
151
187
|
|
152
188
|
|
153
189
|
def test_gather_pauli_blocks():
|
TB2J/symmetrize_J.py
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
from sympair import SymmetryPairFinder, SymmetryPairGroupDict
|
2
|
+
import numpy as np
|
3
|
+
from pathlib import Path
|
4
|
+
from TB2J.versioninfo import print_license
|
5
|
+
import copy
|
6
|
+
|
7
|
+
|
8
|
+
class TB2JSymmetrizer:
|
9
|
+
def __init__(self, exc, symprec=1e-8, verbose=True):
|
10
|
+
# list of pairs with the index of atoms
|
11
|
+
ijRs = exc.ijR_list_index_atom()
|
12
|
+
finder = SymmetryPairFinder(atoms=exc.atoms, pairs=ijRs, symprec=symprec)
|
13
|
+
self.verbose = verbose
|
14
|
+
|
15
|
+
if verbose:
|
16
|
+
print("=" * 30)
|
17
|
+
print_license()
|
18
|
+
print("-" * 30)
|
19
|
+
print(
|
20
|
+
"WARNING: The symmetry detection is based on the crystal symmetry, not the magnetic symmetry. Make sure if this is what you want."
|
21
|
+
)
|
22
|
+
print("-" * 30)
|
23
|
+
if exc.has_dmi:
|
24
|
+
print(
|
25
|
+
"WARNING: Currently only the isotropic exchange is symmetrized. Symmetrization of DMI and anisotropic exchange are not yet implemented."
|
26
|
+
)
|
27
|
+
|
28
|
+
print(f"Finding crystal symmetry with symprec of {symprec} Angstrom.")
|
29
|
+
print("Symmetry found:")
|
30
|
+
print(finder.spacegroup)
|
31
|
+
print(f"-" * 30)
|
32
|
+
self.pgdict = finder.get_symmetry_pair_group_dict()
|
33
|
+
self.exc = exc
|
34
|
+
self.new_exc = copy.deepcopy(exc)
|
35
|
+
|
36
|
+
def print_license(self):
|
37
|
+
print_license()
|
38
|
+
|
39
|
+
def symmetrize_J(self):
|
40
|
+
"""
|
41
|
+
Symmetrize the exchange parameters J.
|
42
|
+
"""
|
43
|
+
symJdict = {}
|
44
|
+
Jdict = self.exc.exchange_Jdict
|
45
|
+
ngroup = self.pgdict
|
46
|
+
for pairgroup in self.pgdict.groups:
|
47
|
+
ijRs = pairgroup.get_all_ijR()
|
48
|
+
ijRs_spin = [self.exc.ijR_index_atom_to_spin(*ijR) for ijR in ijRs]
|
49
|
+
Js = [self.exc.get_J(*ijR_spin) for ijR_spin in ijRs_spin]
|
50
|
+
Javg = np.average(Js)
|
51
|
+
for i, j, R in ijRs_spin:
|
52
|
+
symJdict[(R, i, j)] = Javg
|
53
|
+
self.new_exc.exchange_Jdict = symJdict
|
54
|
+
|
55
|
+
def output(self, path="TB2J_symmetrized"):
|
56
|
+
if path is None:
|
57
|
+
path = Path(".")
|
58
|
+
self.new_exc.write_all(path=path)
|
59
|
+
|
60
|
+
def run(self, path=None):
|
61
|
+
print("** Symmetrizing exchange parameters.")
|
62
|
+
self.symmetrize_J()
|
63
|
+
print("** Outputing the symmetrized exchange parameters.")
|
64
|
+
print(f"** Output path: {path} .")
|
65
|
+
self.output(path=path)
|
66
|
+
print("** Finished.")
|
67
|
+
|
68
|
+
|
69
|
+
def symmetrize_J(
|
70
|
+
exc=None,
|
71
|
+
path=None,
|
72
|
+
fname="TB2J.pickle",
|
73
|
+
symprec=1e-5,
|
74
|
+
output_path="TB2J_symmetrized",
|
75
|
+
):
|
76
|
+
"""
|
77
|
+
symmetrize the exchange parameters
|
78
|
+
parameters:
|
79
|
+
exc: exchange
|
80
|
+
"""
|
81
|
+
if exc is None:
|
82
|
+
exc = SpinIO.load_pickle(path=path, fname=fname)
|
83
|
+
symmetrizer = TB2JSymmetrizer(exc, symprec=symprec)
|
84
|
+
symmetrizer.run(path=output_path)
|
85
|
+
|
86
|
+
|
87
|
+
def symmetrize_J_cli():
|
88
|
+
from argparse import ArgumentParser
|
89
|
+
|
90
|
+
parser = ArgumentParser(
|
91
|
+
description="Symmetrize exchange parameters. Currently, it take the crystal symmetry into account and not the magnetic moment into account."
|
92
|
+
)
|
93
|
+
parser.add_argument(
|
94
|
+
"-i",
|
95
|
+
"--inpath",
|
96
|
+
default=None,
|
97
|
+
help="input path to the exchange parameters",
|
98
|
+
)
|
99
|
+
parser.add_argument(
|
100
|
+
"-o",
|
101
|
+
"--outpath",
|
102
|
+
default="TB2J_results_symmetrized",
|
103
|
+
help="output path to the symmetrized exchange parameters",
|
104
|
+
)
|
105
|
+
parser.add_argument(
|
106
|
+
"-s",
|
107
|
+
"--symprec",
|
108
|
+
type=float,
|
109
|
+
default=1e-5,
|
110
|
+
help="precision for symmetry detection. default is 1e-5 Angstrom",
|
111
|
+
)
|
112
|
+
args = parser.parse_args()
|
113
|
+
if args.inpath is None:
|
114
|
+
parser.print_help()
|
115
|
+
raise ValueError("Please provide the input path to the exchange.")
|
116
|
+
symmetrize_J(path=args.inpath, output_path=args.outpath, symprec=args.symprec)
|
117
|
+
|
118
|
+
|
119
|
+
if __name__ == "__main__":
|
120
|
+
symmetrize_J_cli()
|
TB2J/utils.py
CHANGED
@@ -87,6 +87,7 @@ def auto_assign_wannier_to_atom(positions, atoms, max_distance=0.1, half=False):
|
|
87
87
|
"""
|
88
88
|
pos = np.array(positions)
|
89
89
|
atompos = atoms.get_scaled_positions(wrap=False)
|
90
|
+
cell = atoms.get_cell()
|
90
91
|
ind_atoms = []
|
91
92
|
newpos = []
|
92
93
|
refpos = []
|
@@ -95,8 +96,9 @@ def auto_assign_wannier_to_atom(positions, atoms, max_distance=0.1, half=False):
|
|
95
96
|
dp = p[None, :] - atompos
|
96
97
|
# residual of d
|
97
98
|
r = dp - np.round(dp)
|
99
|
+
r_cart = r @ cell
|
98
100
|
# find the min of residual
|
99
|
-
normd = np.linalg.norm(
|
101
|
+
normd = np.linalg.norm(r_cart, axis=1)
|
100
102
|
iatom = np.argmin(normd)
|
101
103
|
# ref+residual
|
102
104
|
rmin = r[iatom]
|
@@ -330,3 +332,82 @@ def simpson_nonuniform(x, f):
|
|
330
332
|
result += f[N - 1] * (h[N - 1] ** 2 + 3 * h[N - 1] * h[N - 2]) / (6 * h[N - 2])
|
331
333
|
result -= f[N - 2] * h[N - 1] ** 3 / (6 * h[N - 2] * (h[N - 2] + h[N - 1]))
|
332
334
|
return result
|
335
|
+
|
336
|
+
|
337
|
+
def simpson_nonuniform_weight(x):
|
338
|
+
"""
|
339
|
+
Simpson rule for irregularly spaced data.
|
340
|
+
x: list or np.array of floats
|
341
|
+
Sampling points for the function values
|
342
|
+
Returns
|
343
|
+
-------
|
344
|
+
weight : list or np.array of floats
|
345
|
+
weight for the Simpson rule
|
346
|
+
For the function f(x), the integral is approximated as
|
347
|
+
$\int f(x) dx \approx \sum_i weight[i] * f(x[i])$
|
348
|
+
"""
|
349
|
+
|
350
|
+
weight = np.zeros_like(x)
|
351
|
+
N = len(x) - 1
|
352
|
+
h = np.diff(x)
|
353
|
+
|
354
|
+
for i in range(1, N, 2):
|
355
|
+
hph = h[i] + h[i - 1]
|
356
|
+
weight[i] += (h[i] ** 3 + h[i - 1] ** 3 + 3.0 * h[i] * h[i - 1] * hph) / (
|
357
|
+
6 * h[i] * h[i - 1]
|
358
|
+
)
|
359
|
+
weight[i - 1] += (
|
360
|
+
2.0 * h[i - 1] ** 3 - h[i] ** 3 + 3.0 * h[i] * h[i - 1] ** 2
|
361
|
+
) / (6 * h[i - 1] * hph)
|
362
|
+
weight[i + 1] += (
|
363
|
+
2.0 * h[i] ** 3 - h[i - 1] ** 3 + 3.0 * h[i - 1] * h[i] ** 2
|
364
|
+
) / (6 * h[i] * hph)
|
365
|
+
|
366
|
+
if (N + 1) % 2 == 0:
|
367
|
+
weight[N] += (2 * h[N - 1] ** 2 + 3.0 * h[N - 2] * h[N - 1]) / (
|
368
|
+
6 * (h[N - 2] + h[N - 1])
|
369
|
+
)
|
370
|
+
weight[N - 1] += (h[N - 1] ** 2 + 3 * h[N - 1] * h[N - 2]) / (6 * h[N - 2])
|
371
|
+
weight[N - 2] -= h[N - 1] ** 3 / (6 * h[N - 2] * (h[N - 2] + h[N - 1]))
|
372
|
+
return weight
|
373
|
+
|
374
|
+
|
375
|
+
def trapz_nonuniform_weight(x):
|
376
|
+
"""
|
377
|
+
trapezoidal rule for irregularly spaced data.
|
378
|
+
x: list or np.array of floats
|
379
|
+
Sampling points for the function values
|
380
|
+
Returns
|
381
|
+
-------
|
382
|
+
weight : list or np.array of floats
|
383
|
+
weight for the trapezoidal rule
|
384
|
+
For the function f(x), the integral is approximated as
|
385
|
+
$\int f(x) dx \approx \sum_i weight[i] * f(x[i])$
|
386
|
+
"""
|
387
|
+
h = np.diff(x)
|
388
|
+
weight = np.zeros_like(x)
|
389
|
+
weight[0] = h[0] / 2.0
|
390
|
+
weight[1:-1] = (h[1:] + h[:-1]) / 2.0
|
391
|
+
weight[-1] = h[-1] / 2.0
|
392
|
+
return weight
|
393
|
+
|
394
|
+
|
395
|
+
def test_simpson_nonuniform():
|
396
|
+
x = np.array([0.0, 0.1, 0.3, 0.5, 0.8, 1.0])
|
397
|
+
w = simpson_nonuniform_weight(x)
|
398
|
+
# assert np.allclose(w, [0.1, 0.4, 0.4, 0.4, 0.4, 0.1])
|
399
|
+
assert np.allclose(simpson_nonuniform(x, x**8), 0.12714277533333335)
|
400
|
+
print("simpson_weight:", simpson_nonuniform_weight(x) @ x**8, 0.12714277533333335)
|
401
|
+
print("trapz_weight:", trapz_nonuniform_weight(x) @ x**8)
|
402
|
+
|
403
|
+
x2 = np.linspace(0, 1, 500)
|
404
|
+
print(simpson_nonuniform_weight(x2) @ x2**8, 1 / 9.0)
|
405
|
+
print(simpson_nonuniform_weight(x2) @ x2**8)
|
406
|
+
print("simpson_weight:", simpson_nonuniform_weight(x2) @ x2**8)
|
407
|
+
print("trapz_weight:", trapz_nonuniform_weight(x2) @ x2**8)
|
408
|
+
|
409
|
+
assert np.allclose(simpson_nonuniform(x, x**8), 1 / 9.0)
|
410
|
+
|
411
|
+
|
412
|
+
if __name__ == "__main__":
|
413
|
+
test_simpson_nonuniform()
|
@@ -1,8 +1,9 @@
|
|
1
1
|
#!python
|
2
|
-
from TB2J.abacus.gen_exchange_abacus import gen_exchange_abacus
|
3
|
-
from TB2J.versioninfo import print_license
|
4
|
-
import sys
|
5
2
|
import argparse
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from TB2J.interfaces import gen_exchange_abacus
|
6
|
+
from TB2J.versioninfo import print_license
|
6
7
|
|
7
8
|
|
8
9
|
def run_abacus2J():
|
@@ -141,7 +142,7 @@ def run_abacus2J():
|
|
141
142
|
description=args.description,
|
142
143
|
output_path=args.output_path,
|
143
144
|
use_cache=args.use_cache,
|
144
|
-
|
145
|
+
nproc=args.np,
|
145
146
|
exclude_orbs=args.exclude_orbs,
|
146
147
|
orb_decomposition=args.orb_decomposition,
|
147
148
|
)
|