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
TB2J/exchangeCL2.py
CHANGED
@@ -234,11 +234,11 @@ class ExchangeCL2(ExchangeCL):
|
|
234
234
|
self.contour.path, self.JJ_list[(R, iatom, jatom)]
|
235
235
|
)
|
236
236
|
|
237
|
-
def
|
237
|
+
def get_quantities_per_e(self, e):
|
238
238
|
GR_up = self.Gup.get_GR(self.short_Rlist, energy=e, get_rho=False)
|
239
239
|
GR_dn = self.Gdn.get_GR(self.short_Rlist, energy=e, get_rho=False)
|
240
240
|
Jorb_list, JJ_list = self.get_all_A(GR_up, GR_dn)
|
241
|
-
return Jorb_list, JJ_list
|
241
|
+
return dict(Jorb_list=Jorb_list, JJ_list=JJ_list)
|
242
242
|
|
243
243
|
def calculate_all(self):
|
244
244
|
"""
|
@@ -248,13 +248,18 @@ class ExchangeCL2(ExchangeCL):
|
|
248
248
|
|
249
249
|
npole = len(self.contour.path)
|
250
250
|
if self.np == 1:
|
251
|
-
results = map(
|
251
|
+
results = map(
|
252
|
+
self.get_quantities_per_e, tqdm(self.contour.path, total=npole)
|
253
|
+
)
|
252
254
|
else:
|
253
255
|
# pool = ProcessPool(nodes=self.np)
|
254
256
|
# results = pool.map(self.get_AijR_rhoR ,self.contour.path)
|
255
|
-
results = p_map(
|
257
|
+
results = p_map(
|
258
|
+
self.get_quantities_per_e, self.contour.path, num_cpus=self.np
|
259
|
+
)
|
256
260
|
for i, result in enumerate(results):
|
257
|
-
Jorb_list
|
261
|
+
Jorb_list = result["Jorb_list"]
|
262
|
+
JJ_list = result["JJ_list"]
|
258
263
|
for iR, R in enumerate(self.R_ijatom_dict):
|
259
264
|
for iatom, jatom in self.R_ijatom_dict[R]:
|
260
265
|
key = (R, iatom, jatom)
|
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),
|
@@ -281,7 +270,14 @@ class TBGreen:
|
|
281
270
|
# Gk = np.linalg.inv((energy+self.efermi)*self.S[ik,:,:] - self.H[ik,:,:])
|
282
271
|
return Gk
|
283
272
|
|
284
|
-
def
|
273
|
+
def get_Gk_all(self, energy):
|
274
|
+
"""Green's function G(k) for one energy for all kpoints"""
|
275
|
+
Gk_all = np.zeros((self.nkpts, self.nbasis, self.nbasis), dtype=complex)
|
276
|
+
for ik, _ in enumerate(self.kpts):
|
277
|
+
Gk_all[ik] = self.get_Gk(ik, energy)
|
278
|
+
return Gk_all
|
279
|
+
|
280
|
+
def get_GR(self, Rpts, energy, get_rho=False, Gk_all=None):
|
285
281
|
"""calculate real space Green's function for one energy, all R points.
|
286
282
|
G(R, epsilon) = G(k, epsilon) exp(-2\pi i R.dot. k)
|
287
283
|
:param Rpts: R points
|
@@ -293,7 +289,10 @@ class TBGreen:
|
|
293
289
|
GR = defaultdict(lambda: 0.0j)
|
294
290
|
rhoR = defaultdict(lambda: 0.0j)
|
295
291
|
for ik, kpt in enumerate(self.kpts):
|
296
|
-
|
292
|
+
if Gk_all is None:
|
293
|
+
Gk = self.get_Gk(ik, energy)
|
294
|
+
else:
|
295
|
+
Gk = Gk_all[ik]
|
297
296
|
if get_rho:
|
298
297
|
if self.is_orthogonal:
|
299
298
|
rhok = Gk
|
File without changes
|
TB2J/manager.py
CHANGED
@@ -6,7 +6,13 @@ from TB2J.exchangeCL2 import ExchangeCL2
|
|
6
6
|
from TB2J.exchange_qspace import ExchangeCLQspace
|
7
7
|
from TB2J.utils import read_basis, auto_assign_basis_name
|
8
8
|
from ase.io import read
|
9
|
-
|
9
|
+
|
10
|
+
#from TB2J.sisl_wrapper import SislWrapper
|
11
|
+
try:
|
12
|
+
from HamiltonIO.siesta import SislWrapper
|
13
|
+
except ImportError:
|
14
|
+
print("Cannot import SislWrapper from HamiltonIO.siesta. Please install HamiltonIO first.")
|
15
|
+
from TB2J.sisl_wrapper import SislWrapper
|
10
16
|
from TB2J.gpaw_wrapper import GPAWWrapper
|
11
17
|
from TB2J.wannier import parse_atoms
|
12
18
|
|
@@ -283,8 +289,8 @@ def gen_exchange_siesta(
|
|
283
289
|
geom = H.geometry
|
284
290
|
if H.spin.is_colinear:
|
285
291
|
print("Reading Siesta hamiltonian: colinear spin.")
|
286
|
-
tbmodel_up = SislWrapper(H, spin=0, geom=geom)
|
287
|
-
tbmodel_dn = SislWrapper(H, spin=1, geom=geom)
|
292
|
+
tbmodel_up = SislWrapper(fdf_fname=None,sisl_hamiltonian=H, spin=0, geom=geom)
|
293
|
+
tbmodel_dn = SislWrapper(fdf_fname=None, sisl_hamiltonian=H, spin=1, geom=geom)
|
288
294
|
basis = dict(zip(tbmodel_up.orbs, list(range(tbmodel_up.norb))))
|
289
295
|
print("Starting to calculate exchange.")
|
290
296
|
description = f""" Input from collinear Siesta data.
|
@@ -351,7 +357,7 @@ def gen_exchange_siesta(
|
|
351
357
|
|
352
358
|
elif H.spin.is_spinorbit or H.spin.is_noncolinear:
|
353
359
|
print("Reading Siesta hamiltonian: non-colinear spin.")
|
354
|
-
tbmodel = SislWrapper(H, spin=None, geom=geom)
|
360
|
+
tbmodel = SislWrapper(fdf_fname=None, sisl_hamiltonian=H, spin=None, geom=geom)
|
355
361
|
basis = dict(zip(tbmodel.orbs, list(range(tbmodel.nbasis))))
|
356
362
|
print("Starting to calculate exchange.")
|
357
363
|
description = f""" Input from non-collinear Siesta data.
|
@@ -0,0 +1 @@
|
|
1
|
+
from .lowdin import Lowdin
|
TB2J/mathutils/fermi.py
ADDED
@@ -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
|
TB2J/mathutils/lowdin.py
ADDED
@@ -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,56 @@
|
|
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 spherical_to_cartesian(theta, phi, normalize=True):
|
18
|
+
"""
|
19
|
+
Convert spherical coordinates to cartesian
|
20
|
+
"""
|
21
|
+
x = np.sin(theta) * np.cos(phi)
|
22
|
+
y = np.sin(theta) * np.sin(phi)
|
23
|
+
z = np.cos(theta)
|
24
|
+
vec = np.array([x, y, z])
|
25
|
+
if normalize:
|
26
|
+
vec = vec / np.linalg.norm(vec)
|
27
|
+
return vec
|
28
|
+
|
29
|
+
|
30
|
+
def rotate_Matrix_from_z_to_spherical(M, theta, phi, normalize=True):
|
31
|
+
"""
|
32
|
+
Given a spinor matrix M, rotate it from z-axis to spherical coordinates
|
33
|
+
"""
|
34
|
+
axis = spherical_to_cartesian(theta, phi, normalize)
|
35
|
+
return rotate_Matrix_from_z_to_axis(M, axis, normalize)
|
36
|
+
|
37
|
+
|
38
|
+
def test_rotate_Matrix_from_z_to_axis():
|
39
|
+
M = np.array([[1.1, 0], [0, 0.9]])
|
40
|
+
print(pauli_block_all(M))
|
41
|
+
Mnew = rotate_Matrix_from_z_to_axis(M, [1, 1, 1])
|
42
|
+
print(pauli_block_all(Mnew))
|
43
|
+
print(Mnew)
|
44
|
+
|
45
|
+
M = np.array(
|
46
|
+
[
|
47
|
+
[-9.90532976e-06 + 0.0j, 0.00000000e00 + 0.0j],
|
48
|
+
[0.00000000e00 + 0.0j, -9.88431291e-06 + 0.0j],
|
49
|
+
]
|
50
|
+
)
|
51
|
+
print(M)
|
52
|
+
print(rotate_Matrix_from_z_to_axis(M, [0, 0, 1]))
|
53
|
+
|
54
|
+
|
55
|
+
if __name__ == "__main__":
|
56
|
+
test_rotate_Matrix_from_z_to_axis()
|
TB2J/patch.py
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
import types
|
2
|
+
from unittest import mock
|
3
|
+
|
4
|
+
class A(object):#but seems to work for old style objects too
|
5
|
+
def funcx(self,x):
|
6
|
+
print("x1=",x)
|
7
|
+
print("called from", self)
|
8
|
+
|
9
|
+
def method(self,x):
|
10
|
+
print("xmethod=",x)
|
11
|
+
print("called from", self)
|
12
|
+
|
13
|
+
def patch_me(target):
|
14
|
+
def method(target,x):
|
15
|
+
print("x=",x)
|
16
|
+
print("called from", target)
|
17
|
+
target.method = types.MethodType(method,target)
|
18
|
+
|
19
|
+
def method(self,x):
|
20
|
+
print("x=",x)
|
21
|
+
print("called from", self)
|
22
|
+
|
23
|
+
@mock.patch("__main__.A")
|
24
|
+
def funcx(self,x):
|
25
|
+
print("new x=",x)
|
26
|
+
print("called from", self)
|
27
|
+
|
28
|
+
A.method=method
|
29
|
+
#add more if needed
|
30
|
+
a = A()
|
31
|
+
print(A.__dict__)
|
32
|
+
print(a)
|
33
|
+
#out: <__main__.A object at 0x2b73ac88bfd0>
|
34
|
+
|
35
|
+
@mock.patch("__main__.a")
|
36
|
+
def funcx(self,x):
|
37
|
+
print("x=",x)
|
38
|
+
print("called from", self)
|
39
|
+
|
40
|
+
a.funcx(3)
|
41
|
+
patch_me(a) #patch instance
|
42
|
+
a.method=method
|
43
|
+
#a.method(5)
|
44
|
+
#out: x= 5
|
45
|
+
#out: called from <__main__.A object at 0x2b73ac88bfd0>
|
46
|
+
patch_me(A)
|
47
|
+
|
48
|
+
a.method(6) #can patch class too
|
49
|
+
#out: x= 6
|
50
|
+
#out: called from <class '__main__.A'>
|
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/spinham/h_matrix.py
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import matplotlib.pyplot as plt
|
3
|
+
import aiida
|
4
|
+
from aiida_tb2j.data import ExchangeData
|
5
|
+
|
6
|
+
def plot_dispersion(bands, kpoint_labels, color='blue', title=None):
|
7
|
+
|
8
|
+
fig, axs = plt.subplots(1, 1, constrained_layout=True)
|
9
|
+
fig.set_size_inches(6, 6/1.618)
|
10
|
+
|
11
|
+
'''
|
12
|
+
Plot the bands
|
13
|
+
'''
|
14
|
+
kpoints = np.arange(len(bands))
|
15
|
+
axs.plot(kpoints, bands, color=color, linewidth=1.5)
|
16
|
+
|
17
|
+
'''
|
18
|
+
Plot the symmetry points
|
19
|
+
'''
|
20
|
+
bmin = bands.min(); bmax = bands.max()
|
21
|
+
ymin = bmin - 0.05*np.abs(bmin-bmax); ymax = bmax + 0.05*np.abs(bmax-bmin);
|
22
|
+
axs.set_xticks(kpoint_labels[0], kpoint_labels[1], fontsize=10)
|
23
|
+
axs.vlines(x=kpoint_labels[0], ymin=ymin, ymax=ymax, color='black', linewidth=0.5)
|
24
|
+
axs.set_xlim([0, len(kpoints)])
|
25
|
+
axs.set_ylim([ymin, ymax])
|
26
|
+
|
27
|
+
if title is not None:
|
28
|
+
plt.title(title, fontsize=10)
|
29
|
+
|
30
|
+
plt.show()
|
31
|
+
|
32
|
+
|
33
|
+
if __name__ == "__main__":
|
34
|
+
|
35
|
+
from argparse import ArgumentParser
|
36
|
+
|
37
|
+
parser = ArgumentParser()
|
38
|
+
parser.add_argument('-f', '--pickle_filename', type=str, help="Path of the 'TB2J.pickle' file.", required=True)
|
39
|
+
args = parser.parse_args()
|
40
|
+
|
41
|
+
'''
|
42
|
+
Right now the implementation depends on AiiDA and so we must create and load an AiiDA profile,
|
43
|
+
even if we do not store any information on a data base.
|
44
|
+
'''
|
45
|
+
aiida.load_profile()
|
46
|
+
'''
|
47
|
+
Create an ExchangeData object with the informations from the TB2J.pickle file
|
48
|
+
'''
|
49
|
+
exchange = ExchangeData.load_tb2j(pickle_file=args.pickle_filename, isotropic=False, pbc=(True, True, True))
|
50
|
+
'''
|
51
|
+
Compute the magnon band structure along a high symmetry path generated with
|
52
|
+
the ASE package. The informations is stored in an AiiDA BandsData object.
|
53
|
+
Here tol is the symmetry tolerance to determine the space group of the system.
|
54
|
+
They are in units of eV
|
55
|
+
'''
|
56
|
+
magnon_data = exchange.get_magnon_bands(npoints=300, tol=1e-1, with_DMI=True, with_Jani=True)
|
57
|
+
magnon_bands = 1000*magnon_data.get_bands() # Convert to meV
|
58
|
+
raw_labels = [(k, '$\Gamma$') if s == 'GAMMA' else (k, s) for k, s in magnon_data.labels]
|
59
|
+
kpoint_labels = list( zip( *raw_labels ) )
|
60
|
+
plot_dispersion(magnon_bands, kpoint_labels, color='blue', title='Magnon Bands')
|
61
|
+
'''
|
62
|
+
We can also obtain the dynamical matrix h instead of the actual magnon bands. The result
|
63
|
+
is stored in a numpy array with shape (number of kpoints, 2*natoms, 2*natoms)
|
64
|
+
'''
|
65
|
+
kpoints = magnon_data.get_kpoints() # The shape of the kpoints must be (nkpoints, 3)
|
66
|
+
h_matrix = 1000*exchange._H_matrix(kpoints, with_DMI=True, with_Jani=True) # Convert to meV
|
67
|
+
h_dispersion = np.linalg.eigvalsh(h_matrix) # We can also get the eigenvectors with np.linalg.eigh
|
68
|
+
plot_dispersion(h_dispersion, kpoint_labels, color='red', title='h matrix dispersion')
|
TB2J/spinham/obtain_J.py
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from aiida_tb2j.data import ExchangeData
|
3
|
+
from aiida_tb2j.data.exchange import get_rotation_arrays
|
4
|
+
from itertools import combinations_with_replacement
|
5
|
+
|
6
|
+
ux, uy, uz = np.eye(3).reshape((3, 1, 3))
|
7
|
+
|
8
|
+
def combine_arrays(u, v):
|
9
|
+
|
10
|
+
return np.concatenate([u*v, np.roll(u, -1, axis=-1)*v, np.roll(v, -1, axis=-1)*u], axis=-1)
|
11
|
+
|
12
|
+
def get_coefficients(magmoms, indices):
|
13
|
+
|
14
|
+
i, j = indices
|
15
|
+
|
16
|
+
U, V = zip(*[get_rotation_arrays(magmoms, u=u) for u in [ux, uy, uz]])
|
17
|
+
U = np.stack(U).swapaxes(0, 1)
|
18
|
+
V = np.stack(V).swapaxes(0, 1)
|
19
|
+
|
20
|
+
uc = combine_arrays(U[i], U[j].conj())
|
21
|
+
ur = combine_arrays(U[i], U[j])
|
22
|
+
uc2 = combine_arrays(U[i].conj(), U[j])
|
23
|
+
u = np.concatenate([uc, ur, uc2], axis=1)
|
24
|
+
|
25
|
+
return u, V
|
26
|
+
|
27
|
+
def get_C(H0, u, V):
|
28
|
+
|
29
|
+
n = int(H0.shape[-1]/2)
|
30
|
+
upi = np.triu_indices(n)
|
31
|
+
dig = np.diag_indices(n)
|
32
|
+
|
33
|
+
i, j = upi
|
34
|
+
AB0 = H0[:, [i, i, i+n], [j, j+n, j+n]]
|
35
|
+
AB0 = np.swapaxes(AB0, 0, 2).reshape(len(i), 9)
|
36
|
+
J0_flat = np.linalg.solve(u, AB0)
|
37
|
+
|
38
|
+
J0 = np.empty((n, n, 3, 3), dtype=complex)
|
39
|
+
J0[*upi] = J0_flat[:, [0, 6, 5, 3, 1, 7, 8, 4, 2]].reshape(-1, 3, 3)
|
40
|
+
J0 += J0.swapaxes(0, 1)
|
41
|
+
J0[*dig] = 0.0
|
42
|
+
|
43
|
+
C = np.array([np.diag(a) for a in np.einsum('imx,ijxy,jmy->mi', V, 2*J0, V)])
|
44
|
+
|
45
|
+
return C
|
46
|
+
|
47
|
+
def get_J(H, kpoints, exchange):
|
48
|
+
|
49
|
+
n = int(H.shape[-1]/2)
|
50
|
+
upi = np.triu_indices(n)
|
51
|
+
dig = np.diag_indices(n)
|
52
|
+
i, j = upi
|
53
|
+
|
54
|
+
magmoms = exchange.magmoms()[np.unique(exchange.pairs)]
|
55
|
+
magmoms /= np.linalg.norm(magmoms, axis=-1).reshape(-1, 1)
|
56
|
+
u, V = get_coefficients(magmoms, indices=upi)
|
57
|
+
|
58
|
+
H0 = np.stack([1000*exchange._H_matrix(kpoints=np.zeros((1, 3)), with_DMI=True, with_Jani=True, u=u) for u in [ux, uy, uz]])[:, 0, :, :]
|
59
|
+
C = get_C(H0, u, V)
|
60
|
+
H[:, :, :n, :n] += C.reshape(3, 1, n, n)
|
61
|
+
H[:, :, n:, n:] += C.reshape(3, 1, n, n)
|
62
|
+
|
63
|
+
AB = H[:, :, [i, i, i+n], [j, j+n, j+n]]
|
64
|
+
AB[:, :, 2, :] = AB[:, ::-1, 2, :]
|
65
|
+
AB = np.moveaxis(AB, [2, 3], [1, 0]).reshape(len(i), 9, -1)
|
66
|
+
|
67
|
+
vectors = exchange.get_vectors()
|
68
|
+
exp_summand = np.exp( -2j*np.pi*vectors @ kpoints.T )
|
69
|
+
nAB = np.einsum('nik,ndk->nid', AB, exp_summand) / len(kpoints)
|
70
|
+
|
71
|
+
ii = np.where(i == j)
|
72
|
+
i0 = np.where(np.linalg.norm(vectors, axis=-1) == 0.0)
|
73
|
+
J = np.linalg.solve(u, nAB).swapaxes(1, 2)
|
74
|
+
J = J[:, :, [0, 6, 5, 3, 1, 7, 8, 4, 2]].reshape(len(i), -1, 3, 3)
|
75
|
+
J *= -1
|
76
|
+
J[ii] *= 2
|
77
|
+
J[i0] *= 0
|
78
|
+
|
79
|
+
return J
|