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.
Files changed (58) hide show
  1. TB2J/MAE.py +108 -24
  2. TB2J/anisotropy.py +672 -0
  3. TB2J/contour.py +8 -0
  4. TB2J/exchange.py +43 -103
  5. TB2J/exchangeCL2.py +11 -13
  6. TB2J/exchange_params.py +213 -0
  7. TB2J/green.py +62 -27
  8. TB2J/interfaces/__init__.py +12 -0
  9. TB2J/interfaces/abacus/__init__.py +4 -0
  10. TB2J/{abacus → interfaces/abacus}/abacus_api.py +3 -3
  11. TB2J/{abacus → interfaces/abacus}/abacus_wrapper.py +11 -7
  12. TB2J/{abacus → interfaces/abacus}/gen_exchange_abacus.py +6 -3
  13. TB2J/{abacus → interfaces/abacus}/orbital_api.py +4 -4
  14. TB2J/{abacus → interfaces/abacus}/stru_api.py +11 -11
  15. TB2J/{abacus → interfaces/abacus}/test_read_HRSR.py +0 -1
  16. TB2J/{abacus → interfaces/abacus}/test_read_stru.py +5 -3
  17. TB2J/interfaces/gpaw_interface.py +54 -0
  18. TB2J/interfaces/lawaf_interface.py +129 -0
  19. TB2J/interfaces/manager.py +23 -0
  20. TB2J/interfaces/siesta_interface.py +174 -0
  21. TB2J/interfaces/wannier90_interface.py +115 -0
  22. TB2J/io_exchange/io_exchange.py +21 -7
  23. TB2J/io_merge.py +2 -1
  24. TB2J/mathutils/fermi.py +11 -6
  25. TB2J/mathutils/lowdin.py +12 -2
  26. TB2J/mathutils/rotate_spin.py +222 -4
  27. TB2J/pauli.py +38 -2
  28. TB2J/symmetrize_J.py +120 -0
  29. TB2J/utils.py +82 -1
  30. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/abacus2J.py +5 -4
  31. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/siesta2J.py +21 -4
  32. TB2J-0.9.6rc0.data/scripts/wann2J.py +96 -0
  33. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/METADATA +4 -3
  34. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/RECORD +46 -46
  35. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/WHEEL +1 -1
  36. TB2J-0.9.6rc0.dist-info/entry_points.txt +3 -0
  37. TB2J/abacus/MAE.py +0 -320
  38. TB2J/abacus/__init__.py +0 -1
  39. TB2J/abacus/occupations.py +0 -278
  40. TB2J/cut_cell.py +0 -82
  41. TB2J/io_exchange/io_pickle.py +0 -0
  42. TB2J/manager.py +0 -441
  43. TB2J/mathutils.py +0 -12
  44. TB2J/patch.py +0 -50
  45. TB2J/spinham/h_matrix.py +0 -68
  46. TB2J/spinham/obtain_J.py +0 -79
  47. TB2J/supercell.py +0 -532
  48. TB2J-0.9.4rc0.data/scripts/wann2J.py +0 -194
  49. TB2J/{abacus → interfaces/abacus}/test_density_matrix.py +1 -1
  50. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_downfold.py +0 -0
  51. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_eigen.py +0 -0
  52. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_magnon.py +0 -0
  53. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
  54. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_merge.py +0 -0
  55. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_rotate.py +0 -0
  56. {TB2J-0.9.4rc0.data → TB2J-0.9.6rc0.data}/scripts/TB2J_rotateDM.py +0 -0
  57. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/LICENSE +0 -0
  58. {TB2J-0.9.4rc0.dist-info → TB2J-0.9.6rc0.dist-info}/top_level.txt +0 -0
@@ -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
- from TB2J.kpoints import monkhorst_pack
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 datetime import datetime
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
- spinat = self.spinat
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
- MAX_EXP_ARGUMENT = np.log(sys.float_info.max)
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
- warnings.simplefilter("ignore")
20
- ret = np.where(x < MAX_EXP_ARGUMENT, 1 / (1.0 + np.exp(x)), 0.0)
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 inv, eigh
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
- return eigvec @ np.diag(np.sqrt(1.0 / eigval)) @ (eigvec.T.conj())
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
@@ -1,5 +1,7 @@
1
1
  import numpy as np
2
- from TB2J.pauli import pauli_block_all, s0, s1, s2, s3, gather_pauli_blocks
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
- return np.kron(MI, s0) + np.kron(Mx, s1) + np.kron(My, s2) + np.kron(Mz, s3)
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(r, axis=1)
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
- np=args.np,
145
+ nproc=args.np,
145
146
  exclude_orbs=args.exclude_orbs,
146
147
  orb_decomposition=args.orb_decomposition,
147
148
  )