TB2J 0.9.0.1__py3-none-any.whl → 0.9.0.3__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 (30) hide show
  1. TB2J/Jdownfolder.py +110 -24
  2. TB2J/Jtensor.py +1 -1
  3. TB2J/abacus/MAE.py +320 -0
  4. TB2J/abacus/abacus_wrapper.py +20 -2
  5. TB2J/abacus/occupations.py +278 -0
  6. TB2J/abacus/test_density_matrix.py +38 -0
  7. TB2J/green.py +2 -13
  8. TB2J/io_merge.py +2 -1
  9. TB2J/mathutils/__init__.py +1 -0
  10. TB2J/mathutils/fermi.py +22 -0
  11. TB2J/mathutils/kR_convert.py +90 -0
  12. TB2J/mathutils/lowdin.py +12 -0
  13. TB2J/mathutils/rotate_spin.py +35 -0
  14. TB2J/pauli.py +17 -0
  15. TB2J/utils.py +82 -1
  16. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_downfold.py +8 -0
  17. {TB2J-0.9.0.1.dist-info → TB2J-0.9.0.3.dist-info}/METADATA +1 -1
  18. {TB2J-0.9.0.1.dist-info → TB2J-0.9.0.3.dist-info}/RECORD +30 -22
  19. {TB2J-0.9.0.1.dist-info → TB2J-0.9.0.3.dist-info}/WHEEL +1 -1
  20. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_eigen.py +0 -0
  21. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_magnon.py +0 -0
  22. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_magnon_dos.py +0 -0
  23. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_merge.py +0 -0
  24. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_rotate.py +0 -0
  25. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/TB2J_rotateDM.py +0 -0
  26. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/abacus2J.py +0 -0
  27. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/siesta2J.py +0 -0
  28. {TB2J-0.9.0.1.data → TB2J-0.9.0.3.data}/scripts/wann2J.py +0 -0
  29. {TB2J-0.9.0.1.dist-info → TB2J-0.9.0.3.dist-info}/LICENSE +0 -0
  30. {TB2J-0.9.0.1.dist-info → TB2J-0.9.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,278 @@
1
+ """
2
+ This file is stolen from the hotbit programm, with some modification.
3
+ """
4
+
5
+ import numpy as np
6
+ from scipy.optimize import brentq
7
+ import sys
8
+
9
+ from ase.dft.dos import DOS
10
+ from scipy import integrate
11
+
12
+ # import numba
13
+
14
+ # from numba import float64, int32
15
+
16
+ MAX_EXP_ARGUMENT = np.log(sys.float_info.max)
17
+
18
+ # @numba.vectorize(nopython=True)
19
+ # def myfermi(e, mu, width, nspin):
20
+ # x = (e - mu) / width
21
+ # if x < -10:
22
+ # ret = 2.0 / nspin
23
+ # elif x > 10:
24
+ # ret = 0.0
25
+ # else:
26
+ # ret = 2.0 / nspin / (math.exp(x) + 1)
27
+ # return ret
28
+
29
+
30
+ def myfermi(e, mu, width, nspin):
31
+ x = (e - mu) / width
32
+ return np.where(x < 10, 2.0 / (nspin * (np.exp(x) + 1.0)), 0.0)
33
+
34
+
35
+ class Occupations(object):
36
+ def __init__(self, nel, width, wk, nspin=1):
37
+ """
38
+ Initialize parameters for occupations.
39
+ :param nel: Number of electrons
40
+ :param width: Fermi-broadening
41
+ :param wk: k-point weights. eg. If only gamma, [1.0]
42
+ :param nspin(optional): number of spin, if spin=1 multiplicity=2 else, multiplicity=1.
43
+ """
44
+ self.nel = nel
45
+ self.width = width
46
+ self.wk = wk
47
+ self.nk = len(wk)
48
+ self.nspin = nspin
49
+
50
+ def get_mu(self):
51
+ """Return the Fermi-level (or chemical potential)."""
52
+ return self.mu
53
+
54
+ def fermi(self, mu):
55
+ """
56
+ Occupy states with given chemical potential.
57
+ Occupations are 0...2; without k-point weights
58
+ """
59
+ return myfermi(self.e, mu, self.width, self.nspin)
60
+
61
+ def root_function(self, mu):
62
+ """This function is exactly zero when mu is right."""
63
+ f = self.fermi(mu)
64
+ return np.einsum("i, ij->", self.wk, f) - self.nel
65
+
66
+ def occupy(self, e, xtol=1e-11):
67
+ """
68
+ Calculate occupation numbers with given Fermi-broadening.
69
+
70
+ @param e: e[ind_k,ind_orb] energy of k-point, state a
71
+ Note added by hexu: With spin=2,e[k,a,sigma], it also work. only the *2 should be removed.
72
+ @param wk: wk[:] weights for k-points
73
+ @param width: The Fermi-broadening
74
+
75
+ Returns: fermi[ind_k, ind_orb]
76
+ """
77
+ self.e = e
78
+ eflat = e.flatten()
79
+ ind = np.argsort(eflat)
80
+ e_sorted = eflat[ind]
81
+ if self.nspin == 1:
82
+ m = 2
83
+ elif self.nspin == 2:
84
+ m = 1
85
+ n_sorted = (self.wk[:, None, None] * np.ones_like(e) * m).flatten()[ind]
86
+
87
+ sum = n_sorted.cumsum()
88
+ if self.nel < sum[0]:
89
+ ifermi = 0
90
+ elif self.nel > sum[-1]:
91
+ raise ("number of electrons larger than number of orbital*spin")
92
+ else:
93
+ ifermi = np.searchsorted(sum, self.nel)
94
+ try:
95
+ if ifermi == 0:
96
+ elo = e_sorted[0]
97
+ else:
98
+ elo = e_sorted[ifermi - 1]
99
+ if ifermi == len(e_sorted) - 1:
100
+ ehi = e_sorted[-1]
101
+ else:
102
+ ehi = e_sorted[ifermi + 1]
103
+ guess = e_sorted[ifermi]
104
+ dmu = np.max((self.width, guess - elo, ehi - guess))
105
+ mu = brentq(self.root_function, guess - dmu, guess + dmu, xtol=xtol)
106
+ # mu = brent(
107
+ # self.root_function,
108
+ # brack=(guess - elo, guess, guess + dmu),
109
+ # tol=xtol)
110
+ except Exception as E:
111
+ # probably a bad guess
112
+ print("Error in finding Fermi level: ", E)
113
+ dmu = self.width
114
+ if self.nel < 1e-3:
115
+ mu = min(e_sorted) - dmu * 20
116
+ elif self.nel - sum[-1] > -1e-3:
117
+ mu = max(e_sorted) + dmu * 20
118
+ else:
119
+ # mu = brent(
120
+ # self.root_function,
121
+ # brack=(e_sorted[0] - dmu * 10,
122
+ # guess,
123
+ # e_sorted[-1] + dmu * 10),
124
+ # tol=xtol)
125
+ mu = brentq(
126
+ self.root_function,
127
+ e_sorted[0] - dmu * 20,
128
+ e_sorted[-1] + dmu * 20,
129
+ xtol=xtol,
130
+ )
131
+
132
+ if np.abs(self.root_function(mu)) > xtol * 1e4:
133
+ # raise RuntimeError(
134
+ # 'Fermi level could not be assigned reliably. Has the system fragmented?'
135
+ # )
136
+ print(
137
+ "Fermi level could not be assigned reliably. Has the system fragmented?"
138
+ )
139
+
140
+ f = self.fermi(mu)
141
+ # rho=(self.eigenvecs*f).dot(self.eigenvecs.transpose())
142
+
143
+ self.mu, self.f = mu, f
144
+ return f
145
+
146
+ def plot(self):
147
+ import pylab as pl
148
+
149
+ for ik in range(self.nk):
150
+ pl.plot(self.e[ik, :], self.f[ik, :])
151
+ pl.scatter(self.e[ik, :], self.f[ik, :])
152
+ pl.title("occupations")
153
+ pl.xlabel("energy (Ha)")
154
+ pl.ylabel("occupation")
155
+ pl.show()
156
+
157
+
158
+ class GaussOccupations(Occupations):
159
+ def get_mu(self):
160
+ return self.mu
161
+
162
+ def delta(self, energy):
163
+ """Return a delta-function centered at 'energy'."""
164
+ x = -(((self.e - energy) / self.width) ** 2)
165
+ return np.exp(x) / (np.sqrt(np.pi) * self.width)
166
+
167
+ def get_dos(self, npts=500):
168
+ eflat = self.e.flatten()
169
+ ind = np.argsort(eflat)
170
+ ##e_sorted = eflat[ind]
171
+ if self.nspin == 1:
172
+ m = 2
173
+ elif self.nspin == 2:
174
+ m = 1
175
+ # n_sorted = (self.wk * np.ones_like(self.e) * m).flatten()[ind]
176
+ dos = np.zeros(npts)
177
+ for w, e_n in zip(self.w_k, self.e_skn[0]):
178
+ for e in e_n:
179
+ dos += w * self.delta(e)
180
+
181
+ def root_function(self, mu):
182
+ pass
183
+
184
+ # @profile
185
+ def occupy(self, e, xtol=1e-8, guess=0.0):
186
+ self.e = e
187
+ dos = myDOS(kweights=self.wk, eigenvalues=e, width=self.width, npts=501)
188
+ edos = dos.get_energies()
189
+ d = dos.get_dos()
190
+ idos = integrate.cumtrapz(d, edos, initial=0) - self.nel
191
+ # f_idos = interpolate.interp1d(edos, idos)
192
+ # ret = optimize.fmin(f_idos, x0=edos[400], xtol=xtol, disp=True)
193
+ ifermi = np.searchsorted(idos, 0.0)
194
+ # self.mu = ret[0]
195
+ self.mu = edos[ifermi]
196
+ self.f = self.fermi(self.mu)
197
+ return self.f
198
+
199
+
200
+ class myDOS(DOS):
201
+ def __init__(
202
+ self, kweights, eigenvalues, nspin=1, width=0.1, window=None, npts=1001
203
+ ):
204
+ """Electronic Density Of States object.
205
+
206
+ calc: calculator object
207
+ Any ASE compliant calculator object.
208
+ width: float
209
+ Width of guassian smearing. Use width=0.0 for linear tetrahedron
210
+ interpolation.
211
+ window: tuple of two float
212
+ Use ``window=(emin, emax)``. If not specified, a window
213
+ big enough to hold all the eigenvalues will be used.
214
+ npts: int
215
+ Number of points.
216
+
217
+ """
218
+ self.npts = npts
219
+ self.width = width
220
+ # self.w_k = calc.get_k_point_weights()
221
+ self.w_k = kweights
222
+ self.nspins = nspin
223
+ # self.e_skn = np.array([[calc.get_eigenvalues(kpt=k, spin=s)
224
+ # for k in range(len(self.w_k))]
225
+ # for s in range(self.nspins)])
226
+ # self.e_skn -= calc.get_fermi_level()
227
+ self.e_skn = np.array([eigenvalues.T]) # eigenvalues: iband, ikpt
228
+
229
+ if window is None:
230
+ emin = None
231
+ emax = None
232
+ else:
233
+ emin, emax = window
234
+
235
+ if emin is None:
236
+ emin = self.e_skn.min() - 10 * self.width
237
+ if emax is None:
238
+ emax = self.e_skn.max() + 10 * self.width
239
+
240
+ self.energies = np.linspace(emin, emax, npts)
241
+
242
+ # if width == 0.0: # To use tetrahedron method
243
+ # bzkpts = calc.get_bz_k_points()
244
+ # size, offset = get_monkhorst_pack_size_and_offset(bzkpts)
245
+ # bz2ibz = calc.get_bz_to_ibz_map()
246
+ # shape = (self.nspins,) + tuple(size) + (-1,)
247
+ # self.e_skn = self.e_skn[:, bz2ibz].reshape(shape)
248
+ # self.cell = calc.atoms.cell
249
+
250
+ def get_idos(self):
251
+ e, d = self.get_dos()
252
+ return np.trapz(d, e)
253
+
254
+ def delta(self, energy):
255
+ """Return a delta-function centered at 'energy'."""
256
+ x = -(((self.energies - energy) / self.width) ** 2)
257
+ return np.exp(x) / (np.sqrt(np.pi) * self.width)
258
+
259
+ def get_dos(self, spin=None):
260
+ """Get array of DOS values.
261
+
262
+ The *spin* argument can be 0 or 1 (spin up or down) - if not
263
+ specified, the total DOS is returned.
264
+ """
265
+
266
+ if spin is None:
267
+ if self.nspins == 2:
268
+ # Spin-polarized calculation, but no spin specified -
269
+ # return the total DOS:
270
+ return self.get_dos(spin=0) + self.get_dos(spin=1)
271
+ else:
272
+ spin = 0
273
+
274
+ dos = np.zeros(self.npts)
275
+ for w, e_n in zip(self.w_k, self.e_skn[spin]):
276
+ for e in e_n:
277
+ dos += w * self.delta(e)
278
+ return dos
@@ -0,0 +1,38 @@
1
+ from scipy.linalg import eigh
2
+ import numpy as np
3
+
4
+
5
+ def gen_random_hermitean_matrix(n):
6
+ A = np.random.rand(n, n) + 1j * np.random.rand(n, n)
7
+ return A + A.conj().T
8
+
9
+
10
+ def gen_overlap_matrix(n):
11
+ A = np.random.rand(n, n) + 1j * np.random.rand(n, n)
12
+ return np.dot(A, A.conj().T)
13
+
14
+
15
+ def fermi_function(x, ef, beta):
16
+ return 1.0 / (np.exp(beta * (x - ef)) + 1)
17
+
18
+
19
+ def test():
20
+ n = 10
21
+ A = gen_random_hermitean_matrix(n)
22
+ S = gen_overlap_matrix(n)
23
+ beta = 0.1
24
+ ef = 0
25
+
26
+ evals, evecs = eigh(A, S)
27
+
28
+ etot = np.sum(evals * fermi_function(evals, ef, beta))
29
+
30
+ rho = np.einsum("ib,b,jb->ij", evecs, fermi_function(evals, ef, beta), evecs.conj())
31
+
32
+ etot2 = np.trace(np.dot(A, rho))
33
+
34
+ print(etot, etot2)
35
+
36
+
37
+ if __name__ == "__main__":
38
+ test()
TB2J/green.py CHANGED
@@ -7,6 +7,8 @@ import tempfile
7
7
  from pathos.multiprocessing import ProcessPool
8
8
  import sys
9
9
  import pickle
10
+ import warnings
11
+ from TB2J.mathutils.fermi import fermi
10
12
 
11
13
  MAX_EXP_ARGUMENT = np.log(sys.float_info.max)
12
14
 
@@ -26,19 +28,6 @@ def eigen_to_G(evals, evecs, efermi, energy):
26
28
  )
27
29
 
28
30
 
29
- def fermi(e, mu, width=0.01):
30
- """
31
- the fermi function.
32
- .. math::
33
- f=\\frac{1}{\exp((e-\mu)/width)+1}
34
-
35
- :param e,mu,width: e,\mu,width
36
- """
37
-
38
- x = (e - mu) / width
39
- return np.where(x < MAX_EXP_ARGUMENT, 1 / (1.0 + np.exp(x)), 0.0)
40
-
41
-
42
31
  def find_energy_ingap(evals, rbound, gap=4.0):
43
32
  """
44
33
  find a energy inside a gap below rbound (right bound),
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):
@@ -0,0 +1 @@
1
+ from .lowdin import Lowdin
@@ -0,0 +1,22 @@
1
+ import numpy as np
2
+ import warnings
3
+ import sys
4
+
5
+ MAX_EXP_ARGUMENT = np.log(sys.float_info.max)
6
+
7
+
8
+ def fermi(e, mu, width=0.01):
9
+ """
10
+ the fermi function.
11
+ .. math::
12
+ f=\\frac{1}{\exp((e-\mu)/width)+1}
13
+
14
+ :param e,mu,width: e,\mu,width
15
+ """
16
+ x = (e - mu) / width
17
+ # 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)
21
+
22
+ return ret
@@ -0,0 +1,90 @@
1
+ import numpy as np
2
+
3
+
4
+ def HR_to_k(HR, Rlist, kpts):
5
+ # Hk[k,:,:] = sum_R (H[R] exp(i2pi k.R))
6
+ phase = np.exp(2.0j * np.pi * np.tensordot(kpts, Rlist, axes=([1], [1])))
7
+ Hk = np.einsum("rlm, kr -> klm", HR, phase)
8
+ return Hk
9
+
10
+
11
+ def Hk_to_R(Hk, Rlist, kpts, kweights):
12
+ phase = np.exp(-2.0j * np.pi * np.tensordot(kpts, Rlist, axes=([1], [1])))
13
+ HR = np.einsum("klm, kr, k->rlm", Hk, phase, kweights)
14
+ return HR
15
+
16
+
17
+ def k_to_R(kpts, Rlist, Mk, kweights=None):
18
+ """
19
+ Transform k-space wavefunctions to real space.
20
+ params:
21
+ kpts: k-points
22
+ Rlist: list of R vectors
23
+ Mk: matrix of shape [nkpt, n1, n2] in k-space.
24
+
25
+ return:
26
+ MR: matrix of shape [nR, n1, n2], the matrix in R-space.
27
+
28
+ """
29
+ nkpt, n1, n2 = Mk.shape
30
+ if kweights is None:
31
+ kweights = np.ones(nkpt, dtype=float) / nkpt
32
+ phase = np.exp(-2.0j * np.pi * np.tensordot(kpts, Rlist, axes=([1], [1])))
33
+ MR = np.einsum("klm, kr, k -> rlm", Mk, phase, kweights)
34
+ return MR
35
+
36
+ # nkpt, n1, n2 = Mk.shape
37
+ # nR = Rlist.shape[0]
38
+ # MR = np.zeros((nR, n1, n2), dtype=complex)
39
+ # if kweights is None:
40
+ # kweights = np.ones(nkpt, dtype=float)/nkpt
41
+ # for iR, R in enumerate(Rlist):
42
+ # for ik in range(nkpt):
43
+ # MR[iR] += Mk[ik] * np.exp(-2.0j*np.pi * np.dot(kpts[ik], R)) * kweights[ik]
44
+ # return MR
45
+
46
+
47
+ def R_to_k(kpts, Rlist, MR):
48
+ """
49
+ Transform real-space wavefunctions to k-space.
50
+ params:
51
+ kpts: k-points
52
+ Rlist: list of R vectors
53
+ MR: matrix of shape [nR, n1, n2] in R-space.
54
+
55
+ return:
56
+ Mk: matrix of shape [nkpt, n1, n2], the matrix in k-space.
57
+
58
+ """
59
+ phase = np.exp(2.0 * np.pi * 1j * np.tensordot(kpts, Rlist, axes=([1], [1])))
60
+ Mk = np.einsum("rlm, kr -> klm", MR, phase)
61
+
62
+ # nkpt, n1, n2 = Mk.shape
63
+ # nR = Rlist.shape[0]
64
+ # Mk = np.zeros((nkpt, n1, n2), dtype=complex)
65
+ # for iR, R in enumerate(Rlist):
66
+ # for ik in range(nkpt):
67
+ # Mk[ik] += MR[iR] * np.exp(2.0 * np.pi * 1j * np.dot(kpts[ik], R))
68
+ return Mk
69
+
70
+
71
+ def R_to_onek(kpt, Rlist, MR):
72
+ """
73
+ Transform real-space wavefunctions to k-space.
74
+ params:
75
+ kpt: k-point
76
+ Rlist: list of R vectors
77
+ MR: matrix of shape [nR, n1, n2] in R-space.
78
+
79
+ return:
80
+ Mk: matrix of shape [n1, n2], the matrix in k-space.
81
+
82
+ """
83
+ phase = np.exp(2.0j * np.pi * np.dot(Rlist, kpt))
84
+ Mk = np.einsum("rlm, r -> lm", MR, phase)
85
+ return Mk
86
+ # n1, n2 = MR.shape[1:]
87
+ # Mk = np.zeros((n1, n2), dtype=complex)
88
+ # for iR, R in enumerate(Rlist):
89
+ # Mk += MR[iR] * np.exp(2.0j*np.pi * np.dot(kpt, R))
90
+ # return Mk
@@ -0,0 +1,12 @@
1
+ import numpy as np
2
+ from scipy.linalg import inv, eigh
3
+
4
+
5
+ def Lowdin(S):
6
+ """
7
+ Calculate S^(-1/2).
8
+ Which is used in lowind's symmetric orthonormalization.
9
+ psi_prime = S^(-1/2) psi
10
+ """
11
+ eigval, eigvec = eigh(S)
12
+ return eigvec @ np.diag(np.sqrt(1.0 / eigval)) @ (eigvec.T.conj())
@@ -0,0 +1,35 @@
1
+ import numpy as np
2
+ from TB2J.pauli import pauli_block_all, s0, s1, s2, s3, gather_pauli_blocks
3
+
4
+
5
+ def rotate_Matrix_from_z_to_axis(M, axis, normalize=True):
6
+ """
7
+ Given a spinor matrix M, rotate it from z-axis to axis.
8
+ The spinor matrix M is a 2x2 matrix, which can be decomposed as I, x, y, z components using Pauli matrices.
9
+ """
10
+ MI, Mx, My, Mz = pauli_block_all(M)
11
+ axis = axis / np.linalg.norm(axis)
12
+ # M_new = s0* MI + Mz * (axis[0] * s1 + axis[1] * s2 + axis[2] * s3) *2
13
+ M_new = gather_pauli_blocks(MI, Mz * axis[0], Mz * axis[1], Mz * axis[2])
14
+ return M_new
15
+
16
+
17
+ def test_rotate_Matrix_from_z_to_axis():
18
+ M = np.array([[1.1, 0], [0, 0.9]])
19
+ print(pauli_block_all(M))
20
+ Mnew = rotate_Matrix_from_z_to_axis(M, [1, 1, 1])
21
+ print(pauli_block_all(Mnew))
22
+ print(Mnew)
23
+
24
+ M = np.array(
25
+ [
26
+ [-9.90532976e-06 + 0.0j, 0.00000000e00 + 0.0j],
27
+ [0.00000000e00 + 0.0j, -9.88431291e-06 + 0.0j],
28
+ ]
29
+ )
30
+ print(M)
31
+ print(rotate_Matrix_from_z_to_axis(M, [0, 0, 1]))
32
+
33
+
34
+ if __name__ == "__main__":
35
+ test_rotate_Matrix_from_z_to_axis()
TB2J/pauli.py CHANGED
@@ -143,7 +143,24 @@ 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):
147
+ """
148
+ Gather the I, x, y, z component of a matrix.
149
+ """
150
+ return np.kron(MI, s0) + np.kron(Mx, s1) + np.kron(My, s2) + np.kron(Mz, s3)
151
+
152
+
153
+ def test_gather_pauli_blocks():
154
+ M = np.random.rand(4, 4)
155
+ MI, Mx, My, Mz = pauli_block_all(M)
156
+ M2 = gather_pauli_blocks(MI, Mx, My, Mz)
157
+ assert np.allclose(M, M2)
158
+
159
+
146
160
  def op_norm(M):
161
+ """
162
+ Return the operator norm of a matrix.
163
+ """
147
164
  return max(svd(M)[1])
148
165
 
149
166
 
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()
@@ -58,6 +58,13 @@ def main():
58
58
  default=False,
59
59
  )
60
60
 
61
+ parser.add_argument(
62
+ "--method",
63
+ help="The method to downfold the exchange parameter. Options are Lowdin and PWF (projected Wannier function). ",
64
+ type=str,
65
+ default="Lowdin",
66
+ )
67
+
61
68
  args = parser.parse_args()
62
69
 
63
70
  if len(args.metals) == []:
@@ -73,6 +80,7 @@ def main():
73
80
  outpath=args.outpath,
74
81
  qmesh=args.qmesh,
75
82
  iso_only=args.iso_only,
83
+ method=args.method,
76
84
  )
77
85
 
78
86
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: TB2J
3
- Version: 0.9.0.1
3
+ Version: 0.9.0.3
4
4
  Summary: TB2J: First principle to Heisenberg exchange J using tight-binding Green function method
5
5
  Author: Xu He
6
6
  Author-email: mailhexu@gmail.com