TB2J 0.9.0.1__py3-none-any.whl → 0.9.2rc0__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/Jdownfolder.py +110 -24
- TB2J/Jtensor.py +1 -1
- TB2J/MAE.py +185 -0
- TB2J/abacus/MAE.py +320 -0
- TB2J/abacus/abacus_wrapper.py +103 -4
- TB2J/abacus/occupations.py +278 -0
- TB2J/abacus/test_density_matrix.py +38 -0
- TB2J/cut_cell.py +82 -0
- TB2J/exchange.py +99 -73
- TB2J/exchangeCL2.py +10 -5
- TB2J/green.py +14 -15
- TB2J/io_exchange/io_pickle.py +0 -0
- TB2J/manager.py +10 -4
- TB2J/mathutils/__init__.py +1 -0
- TB2J/mathutils/fermi.py +22 -0
- TB2J/mathutils/kR_convert.py +90 -0
- TB2J/mathutils/lowdin.py +12 -0
- TB2J/mathutils/rotate_spin.py +56 -0
- TB2J/patch.py +50 -0
- TB2J/pauli.py +17 -0
- TB2J/spinham/h_matrix.py +68 -0
- TB2J/spinham/obtain_J.py +79 -0
- TB2J/supercell.py +532 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_downfold.py +8 -0
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.2rc0.dist-info}/METADATA +1 -1
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.2rc0.dist-info}/RECORD +38 -23
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.2rc0.dist-info}/WHEEL +1 -1
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_eigen.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_magnon.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_merge.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_rotate.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/TB2J_rotateDM.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/abacus2J.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/siesta2J.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.2rc0.data}/scripts/wann2J.py +0 -0
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.2rc0.dist-info}/LICENSE +0 -0
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.2rc0.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/cut_cell.py
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from TB2J.supercell import find_primitive_cell, map_to_primitive
|
3
|
+
from TB2J.io_exchange.io_exchange import SpinIO, gen_distance_dict
|
4
|
+
|
5
|
+
|
6
|
+
def cut_cell(path, output_path, sc_matrix, origin_atom_id, thr=1e-5):
|
7
|
+
"""
|
8
|
+
Cut the exchange parameters
|
9
|
+
:param path: the original TB2J_results path
|
10
|
+
:param output_path: the output path.
|
11
|
+
:param sc_matrix: the matrix which maps the primitive cell to supercell.
|
12
|
+
:param origin: the origin of the primitive cell.
|
13
|
+
:param thr: the atoms which the reduced position is within -thr to 1.0+thr are considered as inside the primitive atoms
|
14
|
+
:returns:
|
15
|
+
"""
|
16
|
+
sc_matrix = np.asarray(sc_matrix, dtype=int)
|
17
|
+
sc_excparams = SpinIO.load_pickle(path=path, fname='TB2J.pickle')
|
18
|
+
sc_atoms = sc_excparams.atoms
|
19
|
+
uc_atoms, ids = find_primitive_cell(sc_atoms,
|
20
|
+
sc_matrix,
|
21
|
+
origin_atom_id=origin_atom_id,
|
22
|
+
thr=thr,
|
23
|
+
perfect=False)
|
24
|
+
uc_charges = sc_excparams.charges[ids]
|
25
|
+
uc_spinat = sc_excparams.spinat[ids]
|
26
|
+
indmap, Rmap = map_to_primitive(sc_atoms, uc_atoms)
|
27
|
+
|
28
|
+
# TODO index_spin: {iatom: ispin}
|
29
|
+
|
30
|
+
# list of iatom for each spin index.
|
31
|
+
uc_index_spin = [sc_excparams.index_spin[i] for i in ids]
|
32
|
+
|
33
|
+
uc_ind_atoms = {}
|
34
|
+
for iatom, ispin in enumerate(uc_index_spin):
|
35
|
+
if ispin >= 0:
|
36
|
+
uc_ind_atoms[ispin] = iatom
|
37
|
+
|
38
|
+
uc_Jdict = {}
|
39
|
+
uc_Rset = set()
|
40
|
+
for key, val in sc_excparams.exchange_Jdict.items():
|
41
|
+
R, ispin, jspin = key
|
42
|
+
iatom = sc_excparams.ind_atoms[ispin]
|
43
|
+
jatom = sc_excparams.ind_atoms[jspin]
|
44
|
+
uc_ispin = uc_index_spin[indmap[iatom]]
|
45
|
+
uc_jspin = uc_index_spin[indmap[jatom]]
|
46
|
+
uc_R = R @ sc_matrix + Rmap[jatom] - Rmap[iatom]
|
47
|
+
uc_R = tuple(uc_R)
|
48
|
+
if iatom in ids:
|
49
|
+
#print(f"{iatom=}, {indmap[iatom]=},{uc_ispin=}, {uc_jspin=}, {uc_R=} ")
|
50
|
+
uc_Jdict[(uc_R, uc_ispin, uc_jspin)] = val
|
51
|
+
uc_Rset.add(uc_R)
|
52
|
+
|
53
|
+
uc_distance_dict = gen_distance_dict(uc_ind_atoms, uc_atoms, list(uc_Rset))
|
54
|
+
|
55
|
+
assert sc_excparams.colinear, "Cut supercell for non-collinear spin is not yet implemented."
|
56
|
+
|
57
|
+
uc_exc = SpinIO(uc_atoms,
|
58
|
+
spinat=uc_spinat,
|
59
|
+
charges=uc_charges,
|
60
|
+
index_spin=uc_index_spin,
|
61
|
+
colinear=sc_excparams.colinear,
|
62
|
+
distance_dict=uc_distance_dict,
|
63
|
+
exchange_Jdict=uc_Jdict,
|
64
|
+
description="Cutted")
|
65
|
+
|
66
|
+
uc_exc.write_all(output_path)
|
67
|
+
|
68
|
+
|
69
|
+
def run_cut_cell(
|
70
|
+
path="TB2J_results",
|
71
|
+
output_path="./TB2J_cutted",
|
72
|
+
sc_matrix=np.array([[1, 1, 0], [-1, 1, 0], [0, 0, 2]]),
|
73
|
+
):
|
74
|
+
cut_cell(path,
|
75
|
+
output_path,
|
76
|
+
np.array(sc_matrix),
|
77
|
+
origin_atom_id=0,
|
78
|
+
thr=1e-19)
|
79
|
+
|
80
|
+
|
81
|
+
if __name__ == "__main__":
|
82
|
+
run_cut_cell()
|
TB2J/exchange.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
from collections import defaultdict, OrderedDict
|
2
2
|
import os
|
3
3
|
import numpy as np
|
4
|
+
import pickle
|
5
|
+
from dataclasses import dataclass
|
6
|
+
import yaml
|
4
7
|
from TB2J.green import TBGreen
|
5
8
|
from TB2J.pauli import pauli_block_all, pauli_block_sigma_norm, pauli_mat
|
6
9
|
from TB2J.utils import symbol_number, kmesh_to_R
|
@@ -10,25 +13,47 @@ from TB2J.external import p_map
|
|
10
13
|
from TB2J.contour import Contour
|
11
14
|
from TB2J.utils import simpson_nonuniform, trapezoidal_nonuniform, split_symbol_number
|
12
15
|
from TB2J.orbmap import map_orbs_matrix
|
13
|
-
import
|
16
|
+
from typing import List, Tuple
|
14
17
|
|
15
18
|
|
19
|
+
@dataclass
|
16
20
|
class ExchangeParams:
|
21
|
+
"""
|
22
|
+
A class to store the parameters for exchange calculation.
|
23
|
+
"""
|
24
|
+
|
25
|
+
efermi: float
|
26
|
+
basis: list = None
|
27
|
+
magnetic_elements: list = None
|
28
|
+
include_orbs = {}
|
29
|
+
_kmesh = [4, 4, 4]
|
30
|
+
emin: float = -15
|
31
|
+
emax: float = 0.05
|
32
|
+
nz: int = 100
|
33
|
+
exclude_orbs = []
|
34
|
+
ne: int = None
|
35
|
+
Rcut: float = None
|
36
|
+
_use_cache: bool = False
|
37
|
+
np: int = 1
|
38
|
+
description: str = ""
|
39
|
+
write_density_matrix: bool = False
|
40
|
+
orb_decomposition: bool = False
|
41
|
+
output_path: str = "TB2J_results"
|
42
|
+
mae_angles = None
|
43
|
+
|
17
44
|
def __init__(
|
18
45
|
self,
|
19
|
-
efermi,
|
46
|
+
efermi=-10.0,
|
20
47
|
basis=None,
|
21
|
-
magnetic_elements=
|
22
|
-
include_orbs=
|
48
|
+
magnetic_elements=None,
|
49
|
+
include_orbs=None,
|
23
50
|
kmesh=[4, 4, 4],
|
24
|
-
emin=-15,
|
25
|
-
# integration upper bound. Should be 0 (fermi energy). But DFT codes define Fermi energy in various ways.
|
51
|
+
emin=-15,
|
26
52
|
emax=0.05,
|
27
53
|
nz=100,
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
Rcut=None, # Rcut.
|
54
|
+
exclude_orbs=[],
|
55
|
+
ne=None,
|
56
|
+
Rcut=None,
|
32
57
|
use_cache=False,
|
33
58
|
np=1,
|
34
59
|
description="",
|
@@ -37,70 +62,41 @@ class ExchangeParams:
|
|
37
62
|
output_path="TB2J_results",
|
38
63
|
):
|
39
64
|
self.efermi = efermi
|
40
|
-
self.emin = emin
|
41
|
-
self.emax = emax
|
42
|
-
self.nz = nz
|
43
|
-
self.Rcut = Rcut
|
44
65
|
self.basis = basis
|
45
66
|
self.magnetic_elements = magnetic_elements
|
46
67
|
self.include_orbs = include_orbs
|
47
|
-
|
68
|
+
self._kmesh = kmesh
|
69
|
+
self.emin = emin
|
70
|
+
self.emax = emax
|
71
|
+
self.nz = nz
|
48
72
|
self.exclude_orbs = exclude_orbs
|
49
73
|
self.ne = ne
|
74
|
+
self.Rcut = Rcut
|
50
75
|
self._use_cache = use_cache
|
51
76
|
self.np = np
|
52
|
-
self._kmesh = kmesh
|
53
|
-
self.orb_decomposition = orb_decomposition
|
54
|
-
self.write_density_matrix = write_density_matrix
|
55
77
|
self.description = description
|
78
|
+
self.write_density_matrix = write_density_matrix
|
79
|
+
self.orb_decomposition = orb_decomposition
|
56
80
|
self.output_path = output_path
|
57
81
|
|
82
|
+
def set_params(self, **kwargs):
|
83
|
+
for key, val in kwargs.items():
|
84
|
+
setattr(self, key, val)
|
85
|
+
|
86
|
+
def save_to_yaml(self, fname):
|
87
|
+
with open(fname, "w") as myfile:
|
88
|
+
yaml.dump(self.__dict__, myfile)
|
89
|
+
|
58
90
|
|
59
91
|
class Exchange(ExchangeParams):
|
60
92
|
def __init__(
|
61
93
|
self,
|
62
94
|
tbmodels,
|
63
95
|
atoms,
|
64
|
-
|
65
|
-
basis=None,
|
66
|
-
magnetic_elements=[],
|
67
|
-
include_orbs={},
|
68
|
-
kmesh=[4, 4, 4],
|
69
|
-
emin=-15, # integration lower bound, relative to fermi energy
|
70
|
-
# integration upper bound. Should be 0 (fermi energy). But DFT codes define Fermi energy in various ways.
|
71
|
-
emax=0.05,
|
72
|
-
nz=100,
|
73
|
-
# the delta in the (i delta) in green's function to prevent divergence
|
74
|
-
exclude_orbs=[], #
|
75
|
-
ne=None, # number of electrons in Wannier function.
|
76
|
-
Rcut=None, # Rcut.
|
77
|
-
use_cache=False,
|
78
|
-
np=1,
|
79
|
-
description="",
|
80
|
-
write_density_matrix=False,
|
81
|
-
output_path="TB2J_results",
|
82
|
-
orb_decomposition=False,
|
96
|
+
**params: ExchangeParams,
|
83
97
|
):
|
84
98
|
self.atoms = atoms
|
85
|
-
super().__init__(
|
86
|
-
efermi=efermi,
|
87
|
-
basis=basis,
|
88
|
-
magnetic_elements=magnetic_elements,
|
89
|
-
include_orbs=include_orbs,
|
90
|
-
kmesh=kmesh,
|
91
|
-
emin=emin,
|
92
|
-
emax=emax,
|
93
|
-
nz=nz,
|
94
|
-
exclude_orbs=exclude_orbs,
|
95
|
-
ne=ne,
|
96
|
-
Rcut=Rcut,
|
97
|
-
use_cache=use_cache,
|
98
|
-
np=np,
|
99
|
-
description=description,
|
100
|
-
write_density_matrix=write_density_matrix,
|
101
|
-
orb_decomposition=orb_decomposition,
|
102
|
-
output_path=output_path,
|
103
|
-
)
|
99
|
+
super().__init__(**params)
|
104
100
|
self._prepare_kmesh(self._kmesh)
|
105
101
|
self._prepare_Rlist()
|
106
102
|
self.set_tbmodels(tbmodels)
|
@@ -334,7 +330,6 @@ class ExchangeNCL(Exchange):
|
|
334
330
|
"""
|
335
331
|
self.tbmodel = tbmodels
|
336
332
|
self.backend_name = self.tbmodel.name
|
337
|
-
# TODO: check if tbmodels are really a tbmodel with SOC.
|
338
333
|
self.G = TBGreen(
|
339
334
|
self.tbmodel,
|
340
335
|
self.kmesh,
|
@@ -355,6 +350,12 @@ class ExchangeNCL(Exchange):
|
|
355
350
|
if self.write_density_matrix:
|
356
351
|
self.G.write_rho_R()
|
357
352
|
|
353
|
+
def get_MAE(self, thetas, phis):
|
354
|
+
"""
|
355
|
+
Calculate the magnetic anisotropy energy.
|
356
|
+
"""
|
357
|
+
pass
|
358
|
+
|
358
359
|
def _prepare_NijR(self):
|
359
360
|
self.N = {}
|
360
361
|
for R in self.Rlist:
|
@@ -615,7 +616,7 @@ class ExchangeNCL(Exchange):
|
|
615
616
|
def calculate_DMI_NJT(self):
|
616
617
|
"""
|
617
618
|
calculate exchange and DMI with the
|
618
|
-
|
619
|
+
This did not work and should be removed.
|
619
620
|
"""
|
620
621
|
Ddict_NJT = {}
|
621
622
|
Jdict_NJT = {}
|
@@ -679,10 +680,36 @@ class ExchangeNCL(Exchange):
|
|
679
680
|
self.contour.path, AijRs_orb[(R, iatom, jatom)]
|
680
681
|
)
|
681
682
|
|
682
|
-
def
|
683
|
-
|
683
|
+
def get_quantities_per_e(self, e):
|
684
|
+
Gk_all = self.G.get_Gk_all(e)
|
685
|
+
mae = self.get_mae_kspace(Gk_all)
|
686
|
+
# TODO: get the MAE from Gk_all
|
687
|
+
GR = self.G.get_GR(self.short_Rlist, energy=e, get_rho=False, Gk_all=Gk_all)
|
688
|
+
# TODO: define the quantities for one energy.
|
684
689
|
AijR, AijR_orb = self.get_all_A(GR)
|
685
|
-
return AijR, AijR_orb
|
690
|
+
return dict(AijR=AijR, AijR_orb=AijR_orb, mae=mae)
|
691
|
+
|
692
|
+
def get_mae_kspace(self, Gk_all):
|
693
|
+
"""
|
694
|
+
get the MAE from Gk_all
|
695
|
+
TODO: This is only a place holder.
|
696
|
+
"""
|
697
|
+
return None
|
698
|
+
Hso_k = self.model.get_Hso_k(k)
|
699
|
+
mae_e = np.zeros(len(self.mae_angles), dtype=complex)
|
700
|
+
# rotate the Hso_k to angles
|
701
|
+
for ik, k in enumerate(self.G.kpoints):
|
702
|
+
Gk = Gk_all[ik]
|
703
|
+
for i_angle, angle in enumerate(self.mae_angles):
|
704
|
+
# TODO: implementa rotate_H
|
705
|
+
Hso_k_rot = rotate_H(Hso_k, angle)
|
706
|
+
mae_e[i_angle] = Gk @ Hso_k @ Gk @ Hso_k
|
707
|
+
return mae_e
|
708
|
+
|
709
|
+
def get_mae_rspace(self, GR):
|
710
|
+
"""
|
711
|
+
get the MAE from GR
|
712
|
+
"""
|
686
713
|
|
687
714
|
def save_AijR(self, AijRs, fname):
|
688
715
|
result = dict(path=self.contour.path, AijRs=AijRs)
|
@@ -693,6 +720,7 @@ class ExchangeNCL(Exchange):
|
|
693
720
|
"""
|
694
721
|
Do some sanity check before proceding.
|
695
722
|
"""
|
723
|
+
pass
|
696
724
|
|
697
725
|
def calculate_all(self):
|
698
726
|
"""
|
@@ -701,40 +729,38 @@ class ExchangeNCL(Exchange):
|
|
701
729
|
print("Green's function Calculation started.")
|
702
730
|
|
703
731
|
AijRs = {}
|
704
|
-
|
705
732
|
AijRs_orb = {}
|
706
733
|
|
707
734
|
self.validate()
|
708
735
|
|
709
736
|
npole = len(self.contour.path)
|
710
737
|
if self.np > 1:
|
711
|
-
results = p_map(
|
738
|
+
results = p_map(
|
739
|
+
self.get_quantities_per_e, self.contour.path, num_cpus=self.np
|
740
|
+
)
|
712
741
|
else:
|
713
|
-
results = map(
|
742
|
+
results = map(
|
743
|
+
self.get_quantities_per_e, tqdm(self.contour.path, total=npole)
|
744
|
+
)
|
714
745
|
|
715
746
|
for i, result in enumerate(results):
|
716
747
|
for iR, R in enumerate(self.R_ijatom_dict):
|
717
748
|
for iatom, jatom in self.R_ijatom_dict[R]:
|
718
749
|
if (R, iatom, jatom) in AijRs:
|
719
|
-
AijRs[(R, iatom, jatom)].append(result[
|
750
|
+
AijRs[(R, iatom, jatom)].append(result["AijR"][R, iatom, jatom])
|
720
751
|
if self.orb_decomposition:
|
721
752
|
AijRs_orb[(R, iatom, jatom)].append(
|
722
|
-
result[
|
753
|
+
result["AijR_orb"][R, iatom, jatom]
|
723
754
|
)
|
724
755
|
|
725
756
|
else:
|
726
757
|
AijRs[(R, iatom, jatom)] = []
|
727
|
-
AijRs[(R, iatom, jatom)].append(result[
|
758
|
+
AijRs[(R, iatom, jatom)].append(result["AijR"][R, iatom, jatom])
|
728
759
|
if self.orb_decomposition:
|
729
760
|
AijRs_orb[(R, iatom, jatom)] = []
|
730
761
|
AijRs_orb[(R, iatom, jatom)].append(
|
731
|
-
result[
|
762
|
+
result["AijR_orb"][R, iatom, jatom]
|
732
763
|
)
|
733
|
-
if self.np > 1:
|
734
|
-
# executor.close()
|
735
|
-
# executor.join()
|
736
|
-
# executor.clear()
|
737
|
-
pass
|
738
764
|
|
739
765
|
# self.save_AijRs(AijRs)
|
740
766
|
self.integrate(AijRs, AijRs_orb)
|