TB2J 0.9.3rc0__tar.gz → 0.9.9rc2__tar.gz

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 (106) hide show
  1. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/PKG-INFO +4 -3
  2. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/MAE.py +149 -29
  3. tb2j-0.9.9rc2/TB2J/MAEGreen.py +197 -0
  4. tb2j-0.9.9rc2/TB2J/anisotropy.py +672 -0
  5. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/contour.py +8 -0
  6. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/exchange.py +55 -178
  7. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/exchangeCL2.py +11 -13
  8. tb2j-0.9.9rc2/TB2J/exchange_params.py +238 -0
  9. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/green.py +62 -27
  10. tb2j-0.9.9rc2/TB2J/interfaces/__init__.py +12 -0
  11. tb2j-0.9.9rc2/TB2J/interfaces/abacus/__init__.py +4 -0
  12. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/abacus_api.py +3 -3
  13. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/abacus_wrapper.py +19 -10
  14. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/gen_exchange_abacus.py +11 -6
  15. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/orbital_api.py +14 -12
  16. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/stru_api.py +11 -11
  17. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/test_read_HRSR.py +0 -1
  18. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/test_read_stru.py +5 -3
  19. tb2j-0.9.9rc2/TB2J/interfaces/gpaw_interface.py +54 -0
  20. tb2j-0.9.9rc2/TB2J/interfaces/lawaf_interface.py +129 -0
  21. tb2j-0.9.9rc2/TB2J/interfaces/manager.py +23 -0
  22. tb2j-0.9.9rc2/TB2J/interfaces/siesta_interface.py +190 -0
  23. tb2j-0.9.9rc2/TB2J/interfaces/wannier90_interface.py +115 -0
  24. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/io_exchange.py +21 -7
  25. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_merge.py +2 -1
  26. tb2j-0.9.9rc2/TB2J/mathutils/fermi.py +27 -0
  27. tb2j-0.9.9rc2/TB2J/mathutils/lowdin.py +22 -0
  28. tb2j-0.9.9rc2/TB2J/mathutils/rotate_spin.py +274 -0
  29. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/orbmap.py +18 -2
  30. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/pauli.py +38 -2
  31. tb2j-0.9.9rc2/TB2J/symmetrize_J.py +120 -0
  32. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/utils.py +82 -1
  33. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J.egg-info/PKG-INFO +4 -3
  34. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J.egg-info/SOURCES.txt +20 -11
  35. tb2j-0.9.9rc2/TB2J.egg-info/entry_points.txt +3 -0
  36. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J.egg-info/requires.txt +3 -2
  37. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/abacus2J.py +9 -14
  38. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/siesta2J.py +33 -14
  39. tb2j-0.9.9rc2/scripts/wann2J.py +96 -0
  40. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/setup.py +11 -4
  41. tb2j-0.9.3rc0/TB2J/abacus/__init__.py +0 -1
  42. tb2j-0.9.3rc0/TB2J/manager.py +0 -439
  43. tb2j-0.9.3rc0/TB2J/mathutils/fermi.py +0 -22
  44. tb2j-0.9.3rc0/TB2J/mathutils/lowdin.py +0 -12
  45. tb2j-0.9.3rc0/TB2J/mathutils/rotate_spin.py +0 -56
  46. tb2j-0.9.3rc0/TB2J/patch.py +0 -50
  47. tb2j-0.9.3rc0/scripts/wann2J.py +0 -194
  48. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/LICENSE +0 -0
  49. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/README.md +0 -0
  50. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/Jdownfolder.py +0 -0
  51. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/Jtensor.py +0 -0
  52. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/Oiju.py +0 -0
  53. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/Oiju_epc.py +0 -0
  54. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/__init__.py +0 -0
  55. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/basis.py +0 -0
  56. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/citation.py +0 -0
  57. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/density_matrix.py +0 -0
  58. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/epc.py +0 -0
  59. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/exchange_pert.py +0 -0
  60. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/exchange_qspace.py +0 -0
  61. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/external/__init__.py +0 -0
  62. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/external/p_tqdm.py +0 -0
  63. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/gpaw_wrapper.py +0 -0
  64. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/greentest.py +0 -0
  65. {tb2j-0.9.3rc0/TB2J → tb2j-0.9.9rc2/TB2J/interfaces}/abacus/test_density_matrix.py +1 -1
  66. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/__init__.py +0 -0
  67. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/io_multibinit.py +0 -0
  68. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/io_tomsasd.py +0 -0
  69. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/io_txt.py +0 -0
  70. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/io_uppasd.py +0 -0
  71. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/io_exchange/io_vampire.py +0 -0
  72. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/kpoints.py +0 -0
  73. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/mathutils/__init__.py +0 -0
  74. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/mathutils/kR_convert.py +0 -0
  75. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/myTB.py +0 -0
  76. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/pert.py +0 -0
  77. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/plot.py +0 -0
  78. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/rotate_atoms.py +0 -0
  79. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/rotate_siestaDM.py +0 -0
  80. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/sisl_wrapper.py +0 -0
  81. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/__init__.py +0 -0
  82. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/base_parser.py +0 -0
  83. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/constants.py +0 -0
  84. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/hamiltonian.py +0 -0
  85. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/hamiltonian_terms.py +0 -0
  86. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/plot.py +0 -0
  87. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/qsolver.py +0 -0
  88. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/spin_api.py +0 -0
  89. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/spin_xml.py +0 -0
  90. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/spinham/supercell.py +0 -0
  91. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/tensor_rotate.py +0 -0
  92. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/utest.py +0 -0
  93. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/versioninfo.py +0 -0
  94. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/wannier/__init__.py +0 -0
  95. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/wannier/w90_parser.py +0 -0
  96. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J/wannier/w90_tb_parser.py +0 -0
  97. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J.egg-info/dependency_links.txt +0 -0
  98. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/TB2J.egg-info/top_level.txt +0 -0
  99. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_downfold.py +0 -0
  100. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_eigen.py +0 -0
  101. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_magnon.py +0 -0
  102. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_magnon_dos.py +0 -0
  103. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_merge.py +0 -0
  104. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_rotate.py +0 -0
  105. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/scripts/TB2J_rotateDM.py +0 -0
  106. {tb2j-0.9.3rc0 → tb2j-0.9.9rc2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: TB2J
3
- Version: 0.9.3rc0
3
+ Version: 0.9.9rc2
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
@@ -14,14 +14,15 @@ Classifier: Topic :: Scientific/Engineering :: Physics
14
14
  Classifier: License :: OSI Approved :: BSD License
15
15
  Requires-Python: >=3.6
16
16
  License-File: LICENSE
17
- Requires-Dist: numpy>1.16.5
17
+ Requires-Dist: numpy<2.0
18
18
  Requires-Dist: scipy
19
19
  Requires-Dist: matplotlib
20
20
  Requires-Dist: ase>=3.19
21
21
  Requires-Dist: tqdm
22
22
  Requires-Dist: pathos
23
23
  Requires-Dist: packaging>=20.0
24
+ Requires-Dist: HamiltonIO>=0.1.8
24
25
  Requires-Dist: pre-commit
25
- Requires-Dist: HamiltonIO>=0.1.4
26
+ Requires-Dist: sympair>0.1.0
26
27
 
27
28
  TB2J is a Python package aimed to compute automatically the magnetic interactions (superexchange and Dzyaloshinskii-Moriya) between atoms of magnetic crystals from DFT Hamiltonian based on Wannier functions or Linear combination of atomic orbitals. It uses the Green's function method and take the local rigid spin rotation as a perturbation. The package can take the output from Wannier90, which is interfaced with many density functional theory codes or from codes based on localised orbitals. A minimal user input is needed, which allows for an easily integration into a high-throughput workflows.
@@ -1,19 +1,26 @@
1
+ from pathlib import Path
2
+
3
+ import matplotlib.pyplot as plt
1
4
  import numpy as np
2
- from TB2J.abacus.abacus_wrapper import AbacusWrapper, AbacusParser
3
- from TB2J.mathutils.rotate_spin import rotate_Matrix_from_z_to_axis
5
+ import tqdm
6
+
7
+ # from TB2J.abacus.abacus_wrapper import AbacusSplitSOCParser
8
+ from HamiltonIO.abacus.abacus_wrapper import AbacusSplitSOCParser
9
+ from HamiltonIO.model.occupations import Occupations
10
+ from HamiltonIO.siesta import SislParser
11
+ from scipy.linalg import eigh
12
+
13
+ from TB2J.contour import Contour
14
+ from TB2J.green import TBGreen
15
+
16
+ # from HamiltonIO.model.rotate_spin import rotate_Matrix_from_z_to_axis, rotate_Matrix_from_z_to_sperical
4
17
  from TB2J.kpoints import monkhorst_pack
5
- from TB2J.mathutils.fermi import fermi
6
18
  from TB2J.mathutils.kR_convert import R_to_k
7
- from scipy.linalg import eigh
8
- from copy import deepcopy
9
- from scipy.spatial.transform import Rotation
10
- import matplotlib.pyplot as plt
11
- from pathlib import Path
12
- from TB2J.mathutils.rotate_spin import spherical_to_cartesian
13
- from HamiltonIO.model.occupations import Occupations
14
- from TB2J.abacus.abacus_wrapper import AbacusSplitSOCParser
15
- from HamiltonIO.siesta import SislWrapper
16
- import tqdm
19
+
20
+ # from TB2J.abacus.abacus_wrapper import AbacusWrapper, AbacusParser
21
+ from TB2J.mathutils.rotate_spin import (
22
+ rotate_spinor_matrix,
23
+ )
17
24
 
18
25
 
19
26
  def get_occupation(evals, kweights, nel, width=0.1):
@@ -28,12 +35,25 @@ def get_density_matrix(evals=None, evecs=None, kweights=None, nel=None, width=0.
28
35
 
29
36
 
30
37
  class MAE:
31
- def __init__(self, model, kmesh, gamma=True, width=0.1, nel=None):
38
+ def __init__(
39
+ self,
40
+ model,
41
+ kmesh=None,
42
+ gamma=True,
43
+ kpts=None,
44
+ kweights=None,
45
+ width=0.1,
46
+ nel=None,
47
+ ):
32
48
  self.model = model
33
49
  if nel is not None:
34
50
  self.model.nel = nel
35
- self.kpts = monkhorst_pack(kmesh, gamma_center=gamma)
36
- self.kweights = np.ones(len(self.kpts), dtype=float) / len(self.kpts)
51
+ if kpts is None:
52
+ self.kpts = monkhorst_pack(kmesh, gamma_center=gamma)
53
+ self.kweights = np.ones(len(self.kpts), dtype=float) / len(self.kpts)
54
+ else:
55
+ self.kpts = kpts
56
+ self.kweights = kweights
37
57
  self.width = width
38
58
 
39
59
  def get_band_energy(self):
@@ -75,7 +95,10 @@ class MAE:
75
95
  # e,rho = self.model.get_band_energy(dm=True)
76
96
  # self.calc_ref()
77
97
  # thetas = np.linspace(*angle_range, npoints)
78
- for i, (theta, phi) in tqdm.tqdm(enumerate(zip(thetas, phis))):
98
+ nangles = len(thetas)
99
+ for i in tqdm.trange(nangles):
100
+ theta = thetas[i]
101
+ phi = phis[i]
79
102
  self.model.set_Hsoc_rotation_angle([theta, phi])
80
103
  e = self.get_band_energy()
81
104
  es.append(e)
@@ -83,41 +106,138 @@ class MAE:
83
106
  return es
84
107
 
85
108
 
109
+ class MAEGreen(MAE):
110
+ def __init__(
111
+ self,
112
+ model,
113
+ kmesh=None,
114
+ gamma=True,
115
+ kpts=None,
116
+ kweights=None,
117
+ nel=None,
118
+ width=0.1,
119
+ **kwargs,
120
+ ):
121
+ self.model = model
122
+ if nel is not None:
123
+ self.model.nel = nel
124
+ if kpts is None:
125
+ self.kpts = monkhorst_pack(kmesh, gamma_center=gamma)
126
+ self.kweights = np.ones(len(self.kpts), dtype=float) / len(self.kpts)
127
+ else:
128
+ self.kpts = kpts
129
+ self.kweights = kweights
130
+ self.width = width
131
+ model.set_so_strength(0.0)
132
+ evals, evecs = model.solve_all(self.kpts)
133
+ occ = Occupations(
134
+ nel=self.model.nel, width=self.width, wk=self.kweights, nspin=2
135
+ )
136
+ # occ.occupy(evals)
137
+ efermi = occ.efermi(evals)
138
+ print(f"{efermi=}")
139
+ self.G = TBGreen(model, kmesh, efermi=efermi, gamma=gamma, **kwargs)
140
+ self.emin = -12
141
+ self.emax = 0
142
+ self.nz = 50
143
+ self._prepare_elist()
144
+
145
+ def _prepare_elist(self, method="legendre"):
146
+ """
147
+ prepare list of energy for integration.
148
+ The path has three segments:
149
+ emin --1-> emin + 1j*height --2-> emax+1j*height --3-> emax
150
+ """
151
+ self.contour = Contour(self.emin, self.emax)
152
+ # if method.lower() == "rectangle":
153
+ # self.contour.build_path_rectangle(
154
+ # height=self.height, nz1=self.nz1, nz2=self.nz2, nz3=self.nz3
155
+ # )
156
+ if method.lower() == "semicircle":
157
+ self.contour.build_path_semicircle(npoints=self.nz, endpoint=True)
158
+ elif method.lower() == "legendre":
159
+ self.contour.build_path_legendre(npoints=self.nz, endpoint=True)
160
+ else:
161
+ raise ValueError(f"The path cannot be of type {method}.")
162
+
163
+ def get_efermi(self):
164
+ evals, evecs = self.model.solve_all(self.kpts)
165
+ occ = Occupations(
166
+ nel=self.model.nel, width=self.model.width, wk=self.kweights, nspin=2
167
+ )
168
+ occ.get_efermi(evals, self.kweights)
169
+
170
+ def get_perturbed(self, e, thetas, phis):
171
+ G0K = self.G.get_Gk_all(e)
172
+ Hsoc_k = self.model.get_Hk_soc(self.kpts)
173
+ dE_ang = []
174
+ for theta, phi in zip(thetas, phis):
175
+ dE = 0.0
176
+ for i, dHk in enumerate(Hsoc_k):
177
+ dHi = rotate_spinor_matrix(dHk, theta, phi)
178
+ GdH = G0K[i] @ dHi
179
+ # dE += np.trace(GdH @ G0K[i].T.conj() @ dHi) * self.kweights[i]
180
+ dE += np.trace(GdH @ GdH) * self.kweights[i]
181
+ dE_ang.append(dE)
182
+ return np.array(dE_ang)
183
+
184
+ def get_band_energy_vs_angles(self, thetas, phis):
185
+ es = np.zeros(len(thetas))
186
+ for ie, e in enumerate(tqdm.tqdm(self.contour.path)):
187
+ dE_angle = self.get_perturbed(e, thetas, phis)
188
+ es += np.imag(dE_angle * self.contour.weights[ie])
189
+ return -es / np.pi
190
+
191
+
86
192
  def get_model_energy(model, kmesh, gamma=True):
87
193
  ham = MAE(model, kmesh, gamma=gamma)
88
194
  return ham.get_band_energy()
89
195
 
90
196
 
91
197
  def abacus_get_MAE(
92
- path_nosoc, path_soc, kmesh, thetas, psis, gamma=True, outfile="MAE.txt", nel=None
198
+ path_nosoc,
199
+ path_soc,
200
+ kmesh,
201
+ thetas,
202
+ phis,
203
+ gamma=True,
204
+ outfile="MAE.txt",
205
+ nel=None,
206
+ width=0.1,
93
207
  ):
94
208
  """Get MAE from Abacus with magnetic force theorem. Two calculations are needed. First we do an calculation with SOC but the soc_lambda is set to 0. Save the density. The next calculatin we start with the density from the first calculation and set the SOC prefactor to 1. With the information from the two calcualtions, we can get the band energy with magnetic moments in the direction, specified in two list, thetas, and phis."""
95
209
  parser = AbacusSplitSOCParser(
96
210
  outpath_nosoc=path_nosoc, outpath_soc=path_soc, binary=False
97
211
  )
98
212
  model = parser.parse()
99
- model.nel = 16
100
- ham = MAE(model, kmesh, gamma=gamma)
101
- es = ham.get_band_energy_vs_angles(thetas, psis)
213
+ if nel is not None:
214
+ model.nel = nel
215
+ ham = MAEGreen(model, kmesh, gamma=gamma, width=width)
216
+ es = ham.get_band_energy_vs_angles(thetas, phis)
102
217
  if outfile:
103
218
  with open(outfile, "w") as f:
104
- f.write("#theta, psi, energy\n")
105
- for theta, psi, e in zip(thetas, psis, es):
106
- f.write(f"{theta}, {psi}, {e}\n")
219
+ f.write("#theta, phi, energy\n")
220
+ for theta, phi, e in zip(thetas, phis, es):
221
+ f.write(f"{theta:5.3f}, {phi:5.3f}, {e:10.9f}\n")
107
222
  return es
108
223
 
109
224
 
110
- def siesta_get_MAE(fdf_fname, kmesh, thetas, phis, gamma=True, outfile="MAE.txt"):
225
+ def siesta_get_MAE(
226
+ fdf_fname, kmesh, thetas, phis, gamma=True, outfile="MAE.txt", nel=None, width=0.1
227
+ ):
111
228
  """ """
112
- parser = SiestaParser(fdf_fname=fdf_fname, read_H_soc=True)
113
- model = parser.parse()
114
- ham = MAE(model, kmesh, gamma=gamma)
229
+ model = SislParser(fdf_fname=fdf_fname, read_H_soc=True).get_model()
230
+ if nel is not None:
231
+ model.nel = nel
232
+ ham = MAEGreen(model, kmesh, gamma=gamma, width=width)
233
+ # es = ham.get_band_energy_vs_angles(thetas, phis)
115
234
  es = ham.get_band_energy_vs_angles(thetas, phis)
116
235
  if outfile:
117
236
  with open(outfile, "w") as f:
118
237
  f.write("#theta, psi, energy\n")
119
238
  for theta, psi, e in zip(thetas, phis, es):
120
- f.write(f"{theta}, {psi}, {e}\n")
239
+ # f.write(f"{theta}, {psi}, {e}\n")
240
+ f.write(f"{theta:5.3f}, {psi:5.3f}, {e:10.9f}\n")
121
241
  return es
122
242
 
123
243
 
@@ -0,0 +1,197 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+ import tqdm
5
+ from typing_extensions import DefaultDict
6
+
7
+ from HamiltonIO.abacus.abacus_wrapper import AbacusSplitSOCParser
8
+ from TB2J.exchange import ExchangeNCL
9
+
10
+ # from HamiltonIO.model.rotate_spin import rotate_Matrix_from_z_to_axis, rotate_Matrix_from_z_to_sperical
11
+ # from TB2J.abacus.abacus_wrapper import AbacusWrapper, AbacusParser
12
+ from TB2J.mathutils.rotate_spin import (
13
+ rotate_spinor_matrix,
14
+ )
15
+
16
+
17
+ class MAEGreen(ExchangeNCL):
18
+ def __init__(self, angles=[], **kwargs):
19
+ super().__init__(**kwargs)
20
+ self.natoms = len(self.atoms)
21
+ nangles = len(angles[0])
22
+ self.es = np.zeros(nangles, dtype=complex)
23
+ self.es_atom = np.zeros((nangles, self.natoms), dtype=complex)
24
+ self.es_atom_orb = DefaultDict(lambda: 0)
25
+ if angles is None or angles == "axis":
26
+ self.set_angles_axis()
27
+ elif angles == "scan":
28
+ self.set_angles_scan()
29
+ else:
30
+ self.thetas = angles[0]
31
+ self.phis = angles[1]
32
+
33
+ def set_angles_axis(self):
34
+ """theta and phi are defined as the x, y, z, axis."""
35
+ self.thetas = [0, np.pi / 2, np.pi / 2, np.pi / 2]
36
+ self.phis = [0, 0, np.pi / 2, np.pi / 4]
37
+
38
+ def set_angles_scan(self, step=15):
39
+ self.thetas = []
40
+ self.phis = []
41
+ for i in range(0, 181, step):
42
+ for j in range(0, 181, step):
43
+ self.thetas.append(i * np.pi / 180)
44
+ self.phis.append(j * np.pi / 180)
45
+
46
+ def get_perturbed(self, e, thetas, phis):
47
+ self.tbmodel.set_so_strength(0.0)
48
+ G0K = self.G.get_Gk_all(e)
49
+ Hsoc_k = self.tbmodel.get_Hk_soc(self.G.kpts)
50
+ na = len(thetas)
51
+ dE_angle = np.zeros(na, dtype=complex)
52
+ dE_angle_atom = np.zeros((na, self.natoms), dtype=complex)
53
+ # dE_angle_orbitals = np.zeros((na, self.natoms, self.norb, self.norb), dtype=complex)
54
+ # dE_angle_orbitals = DefaultDict(lambda: 0)
55
+ dE_angle_atom_orb = DefaultDict(lambda: 0)
56
+ for iangle, (theta, phi) in enumerate(zip(thetas, phis)):
57
+ for ik, dHk in enumerate(Hsoc_k):
58
+ dHi = rotate_spinor_matrix(dHk, theta, phi)
59
+ GdH = G0K[ik] @ dHi
60
+ # dE += np.trace(GdH @ G0K[i].T.conj() @ dHi) * self.kweights[i]
61
+ # diagonal of second order perturbation.
62
+ # dG2diag = np.diag(GdH @ GdH)
63
+ #dG2 = np.einsum("ij,ji->ij", GdH, GdH)
64
+ dG2 = GdH * GdH.T
65
+ dG2sum = np.sum(dG2)
66
+
67
+ # dG2diag = np.diag(GdH @G0K[i].T.conj() @ dHi)
68
+ #dE_angle[iangle] += np.trace(GdH@GdH) * self.G.kweights[ik]
69
+ #dE_angle[iangle] += np.trace(GdH@G0K[ik].T.conj()@dHi ) * self.G.kweights[ik]
70
+ dE_angle[iangle] += dG2sum * self.G.kweights[ik]
71
+ for iatom in range(self.natoms):
72
+ iorb = self.iorb(iatom)
73
+ #dG2= dG2[::2, ::2] + dG2[1::2, 1::2] + dG2[1::2, ::2] + dG2[::2, 1::2]
74
+ dE_atom_orb = dG2[np.ix_(iorb, iorb)] * self.G.kweights[ik]
75
+ dE_atom_orb = dE_atom_orb[::2, ::2] + dE_atom_orb[1::2, 1::2] + dE_atom_orb[1::2, ::2] + dE_atom_orb[::2, 1::2]
76
+ mmat = self.mmats[iatom]
77
+ dE_atom_orb = mmat.T @ dE_atom_orb @ mmat
78
+
79
+ dE_angle_atom_orb[(iangle, iatom)] += dE_atom_orb
80
+
81
+ dE_atom = np.sum(dE_atom_orb)
82
+ dE_angle_atom[iangle, iatom] += dE_atom
83
+ return dE_angle, dE_angle_atom, dE_angle_atom_orb
84
+
85
+ def get_perturbed_R(self, e, thetas, phis):
86
+ self.tbmodel.set_so_strength(0.0)
87
+ # Here only the first R vector is considered.
88
+ # TODO: consider all R vectors.
89
+ # Rlist = np.zeros((1, 3), dtype=float)
90
+ # G0K = self.G.get_Gk_all(e)
91
+ # G0R = k_to_R(self.G.kpts, Rlist, G0K, self.G.kweights)
92
+ # dE_angle = np.zeros(len(thetas), dtype=complex)
93
+ # dE_angle_atoms = np.zeros((len(thetas), self.natoms), dtype=complex)
94
+ pass
95
+
96
+ def get_band_energy_vs_angles(self, thetas, phis):
97
+ for ie, e in enumerate(tqdm.tqdm(self.contour.path)):
98
+ dE_angle, dE_angle_atom, dE_angle_atom_orb = self.get_perturbed(
99
+ e, thetas, phis
100
+ )
101
+ self.es += dE_angle * self.contour.weights[ie]
102
+ self.es_atom += dE_angle_atom * self.contour.weights[ie]
103
+ for key, value in dE_angle_atom_orb.items():
104
+ self.es_atom_orb[key] += dE_angle_atom_orb[key] * self.contour.weights[ie]
105
+
106
+ self.es = -np.imag(self.es) / np.pi
107
+ self.es_atom = -np.imag(self.es_atom) / np.pi
108
+ for key, value in self.es_atom_orb.items():
109
+ self.es_atom_orb[key] = -np.imag(value) / np.pi
110
+
111
+ def output(self, output_path="TB2J_anisotropy"):
112
+ Path(output_path).mkdir(exist_ok=True)
113
+ fname = f"{output_path}/MAE.dat"
114
+ fname_orb = f"{output_path}/MAE_orb.dat"
115
+ with open(fname, "w") as f:
116
+ f.write("# theta, phi, MAE(total), MAE(atom-wise) Unit: meV\n")
117
+ for i, (theta, phi, e, es) in enumerate(
118
+ zip(self.thetas, self.phis, self.es, self.es_atom)
119
+ ):
120
+ f.write(f"{theta:.5f} {phi:.5f} {e*1e3:.8f} ")
121
+ for ea in es:
122
+ f.write(f"{ea*1e3:.8f} ")
123
+ f.write("\n")
124
+
125
+ with open(fname_orb, "w") as f:
126
+ f.write("=" * 80 + "\n")
127
+ f.write("Orbitals for each atom: \n")
128
+ f.write("=" * 80 + "\n")
129
+ f.write("Note: the energies are in meV\n")
130
+ for iatom in range(self.natoms):
131
+ f.write(f"Atom {iatom:03d}: ")
132
+ for orb in self.orbital_names[iatom]:
133
+ f.write(f"{orb} ")
134
+ f.write("\n")
135
+ for i, (theta, phi, e, eatom) in enumerate(
136
+ zip(self.thetas, self.phis, self.es, self.es_atom)
137
+ ):
138
+ f.write("-" * 60 + "\n")
139
+ f.write(f"Angle {i:03d}: theta={theta:.5f} phi={phi:.5f} \n ")
140
+ f.write(f"E: {e*1e3:.8f} \n")
141
+ for iatom, ea in enumerate(eatom):
142
+ f.write(f"Atom {iatom:03d}: {ea*1e3:.8f} \n")
143
+ f.write("Orbital: ")
144
+ eorb = self.es_atom_orb[(i, iatom)]
145
+
146
+ # write numpy matrix to file
147
+ f.write(
148
+ np.array2string(
149
+ eorb*1e3, precision=4, separator=",", suppress_small=True
150
+ )
151
+ )
152
+
153
+ eorb_diff = eorb - self.es_atom_orb[(0, iatom)]
154
+ f.write("Diference to the first angle: ")
155
+ f.write(
156
+ np.array2string(
157
+ eorb_diff*1e3, precision=4, separator=",", suppress_small=True
158
+ )
159
+ )
160
+ f.write("\n")
161
+
162
+ def run(self, output_path="TB2J_anisotropy"):
163
+ self.get_band_energy_vs_angles(self.thetas, self.phis)
164
+ self.output(output_path=output_path)
165
+
166
+
167
+ def abacus_get_MAE(
168
+ path_nosoc,
169
+ path_soc,
170
+ kmesh,
171
+ thetas,
172
+ phis,
173
+ gamma=True,
174
+ output_path="TB2J_anisotropy",
175
+ magnetic_elements=None,
176
+ nel=None,
177
+ width=0.1,
178
+ **kwargs
179
+ ):
180
+ """Get MAE from Abacus with magnetic force theorem. Two calculations are needed. First we do an calculation with SOC but the soc_lambda is set to 0. Save the density. The next calculatin we start with the density from the first calculation and set the SOC prefactor to 1. With the information from the two calcualtions, we can get the band energy with magnetic moments in the direction, specified in two list, thetas, and phis."""
181
+ parser = AbacusSplitSOCParser(
182
+ outpath_nosoc=path_nosoc, outpath_soc=path_soc, binary=False
183
+ )
184
+ model = parser.parse()
185
+ if nel is not None:
186
+ model.nel = nel
187
+ mae = MAEGreen(tbmodels=model, atoms = model.atoms, kmesh=kmesh, efermi=None, basis=model.basis, angles=[thetas, phis], magnetic_elements=magnetic_elements, **kwargs)
188
+ mae.run(output_path=output_path)
189
+ #es = mae.get_band_energy_vs_angles(thetas, phis)
190
+ #if outfile:
191
+ # with open(outfile, "w") as f:
192
+ # f.write("#theta, phi, energy\n")
193
+ # for theta, phi, e in zip(thetas, phis, es):
194
+ # f.write(f"{theta:5.3f}, {phi:5.3f}, {e:10.9f}\n")
195
+ #return es
196
+
197
+