TB2J 0.9.0.1__py3-none-any.whl → 0.9.1rc0__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/abacus/MAE.py +320 -0
- TB2J/abacus/abacus_wrapper.py +20 -2
- TB2J/abacus/occupations.py +278 -0
- TB2J/abacus/test_density_matrix.py +38 -0
- TB2J/cut_cell.py +82 -0
- TB2J/green.py +2 -13
- TB2J/io_exchange/io_pickle.py +0 -0
- 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 +35 -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.dist-info → TB2J-0.9.1rc0.dist-info}/METADATA +1 -1
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/RECORD +31 -18
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/WHEEL +1 -1
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_downfold.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_eigen.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_magnon.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_merge.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_rotate.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_rotateDM.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/abacus2J.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/siesta2J.py +0 -0
- {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/wann2J.py +0 -0
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/LICENSE +0 -0
- {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/top_level.txt +0 -0
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/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),
|
File without changes
|
@@ -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,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/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
|