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.
Files changed (31) hide show
  1. TB2J/abacus/MAE.py +320 -0
  2. TB2J/abacus/abacus_wrapper.py +20 -2
  3. TB2J/abacus/occupations.py +278 -0
  4. TB2J/abacus/test_density_matrix.py +38 -0
  5. TB2J/cut_cell.py +82 -0
  6. TB2J/green.py +2 -13
  7. TB2J/io_exchange/io_pickle.py +0 -0
  8. TB2J/mathutils/__init__.py +1 -0
  9. TB2J/mathutils/fermi.py +22 -0
  10. TB2J/mathutils/kR_convert.py +90 -0
  11. TB2J/mathutils/lowdin.py +12 -0
  12. TB2J/mathutils/rotate_spin.py +35 -0
  13. TB2J/pauli.py +17 -0
  14. TB2J/spinham/h_matrix.py +68 -0
  15. TB2J/spinham/obtain_J.py +79 -0
  16. TB2J/supercell.py +532 -0
  17. {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/METADATA +1 -1
  18. {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/RECORD +31 -18
  19. {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/WHEEL +1 -1
  20. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_downfold.py +0 -0
  21. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_eigen.py +0 -0
  22. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_magnon.py +0 -0
  23. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_magnon_dos.py +0 -0
  24. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_merge.py +0 -0
  25. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_rotate.py +0 -0
  26. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/TB2J_rotateDM.py +0 -0
  27. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/abacus2J.py +0 -0
  28. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/siesta2J.py +0 -0
  29. {TB2J-0.9.0.1.data → TB2J-0.9.1rc0.data}/scripts/wann2J.py +0 -0
  30. {TB2J-0.9.0.1.dist-info → TB2J-0.9.1rc0.dist-info}/LICENSE +0 -0
  31. {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
@@ -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
 
@@ -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')
@@ -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