TB2J 0.9.12.13__py3-none-any.whl → 0.9.12.15__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.
Potentially problematic release.
This version of TB2J might be problematic. Click here for more details.
- TB2J/MAEGreen.py +78 -59
- TB2J/contour.py +3 -2
- TB2J/exchange.py +335 -47
- TB2J/exchangeCL2.py +283 -47
- TB2J/exchange_params.py +24 -0
- TB2J/green.py +58 -33
- TB2J/myTB.py +11 -11
- TB2J/pauli.py +32 -2
- {tb2j-0.9.12.13.dist-info → tb2j-0.9.12.15.dist-info}/METADATA +4 -5
- {tb2j-0.9.12.13.dist-info → tb2j-0.9.12.15.dist-info}/RECORD +14 -14
- {tb2j-0.9.12.13.dist-info → tb2j-0.9.12.15.dist-info}/WHEEL +0 -0
- {tb2j-0.9.12.13.dist-info → tb2j-0.9.12.15.dist-info}/entry_points.txt +0 -0
- {tb2j-0.9.12.13.dist-info → tb2j-0.9.12.15.dist-info}/licenses/LICENSE +0 -0
- {tb2j-0.9.12.13.dist-info → tb2j-0.9.12.15.dist-info}/top_level.txt +0 -0
TB2J/exchangeCL2.py
CHANGED
|
@@ -4,6 +4,7 @@ Exchange from Green's function
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from collections import defaultdict
|
|
7
|
+
from itertools import product
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
from tqdm import tqdm
|
|
@@ -76,8 +77,8 @@ class ExchangeCL2(ExchangeCL):
|
|
|
76
77
|
del self.Gdn.tbmodel
|
|
77
78
|
|
|
78
79
|
def _adjust_emin(self):
|
|
79
|
-
emin_up = self.Gup.
|
|
80
|
-
emin_dn = self.Gdn.
|
|
80
|
+
emin_up = self.Gup.adjusted_emin
|
|
81
|
+
emin_dn = self.Gdn.adjusted_emin
|
|
81
82
|
self.emin = min(emin_up, emin_dn)
|
|
82
83
|
print(f"A gap is found at {self.emin}, set emin to it.")
|
|
83
84
|
|
|
@@ -108,11 +109,15 @@ class ExchangeCL2(ExchangeCL):
|
|
|
108
109
|
Rij_done = set()
|
|
109
110
|
Jorb_list = dict()
|
|
110
111
|
JJ_list = dict()
|
|
111
|
-
for
|
|
112
|
-
if (iatom, jatom) in ijpairs and (
|
|
113
|
-
Gij_up = self.GR_atom(Gup[
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
for iR, ijpairs in self.R_ijatom_dict.items():
|
|
113
|
+
if (iatom, jatom) in ijpairs and (iR, iatom, jatom) not in Rij_done:
|
|
114
|
+
Gij_up = self.GR_atom(Gup[iR], iatom, jatom)
|
|
115
|
+
iRm = self.R_negative_index[iR]
|
|
116
|
+
if iRm is None:
|
|
117
|
+
R_vec = self.R_vectors[iR]
|
|
118
|
+
Rm_vec = tuple(-x for x in R_vec)
|
|
119
|
+
raise KeyError(f"Negative R vector {Rm_vec} not found in R_vectors")
|
|
120
|
+
Gji_dn = self.GR_atom(Gdn[iRm], jatom, iatom)
|
|
116
121
|
tmp = 0.0j
|
|
117
122
|
Deltai = self.get_Delta(iatom)
|
|
118
123
|
Deltaj = self.get_Delta(jatom)
|
|
@@ -132,13 +137,13 @@ class ExchangeCL2(ExchangeCL):
|
|
|
132
137
|
# np.matmul(Deltaj, Gji_down),
|
|
133
138
|
# )
|
|
134
139
|
tmp = np.sum(t)
|
|
135
|
-
self.Jorb_list[(
|
|
136
|
-
self.JJ_list[(
|
|
137
|
-
Rij_done.add((
|
|
138
|
-
if (
|
|
139
|
-
Jorb_list[(
|
|
140
|
-
JJ_list[(
|
|
141
|
-
Rij_done.add((
|
|
140
|
+
self.Jorb_list[(iR, iatom, jatom)].append(t / (4.0 * np.pi))
|
|
141
|
+
self.JJ_list[(iR, iatom, jatom)].append(tmp / (4.0 * np.pi))
|
|
142
|
+
Rij_done.add((iR, iatom, jatom))
|
|
143
|
+
if (iRm, jatom, iatom) not in Rij_done:
|
|
144
|
+
Jorb_list[(iRm, jatom, iatom)] = t.T / (4.0 * np.pi)
|
|
145
|
+
JJ_list[(iRm, jatom, iatom)] = tmp / (4.0 * np.pi)
|
|
146
|
+
Rij_done.add((iRm, jatom, iatom))
|
|
142
147
|
return Jorb_list, JJ_list
|
|
143
148
|
|
|
144
149
|
def get_all_A(self, Gup, Gdn):
|
|
@@ -151,16 +156,23 @@ class ExchangeCL2(ExchangeCL):
|
|
|
151
156
|
Rij_done = set()
|
|
152
157
|
Jorb_list = dict()
|
|
153
158
|
JJ_list = dict()
|
|
154
|
-
for
|
|
155
|
-
for iatom, jatom in
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
if
|
|
159
|
+
for iR in self.R_ijatom_dict:
|
|
160
|
+
for iatom, jatom in self.R_ijatom_dict[iR]:
|
|
161
|
+
if (iR, iatom, jatom) not in Rij_done:
|
|
162
|
+
iRm = self.R_negative_index[iR]
|
|
163
|
+
if iRm is None:
|
|
164
|
+
R_vec = self.short_Rlist[iR]
|
|
165
|
+
Rm_vec = tuple(-x for x in R_vec)
|
|
159
166
|
raise KeyError(
|
|
160
|
-
f"
|
|
167
|
+
f"Negative R vector {Rm_vec} not found in short_Rlist"
|
|
161
168
|
)
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
|
|
170
|
+
if (iRm, jatom, iatom) in Rij_done:
|
|
171
|
+
raise KeyError(
|
|
172
|
+
f"Strange (iRm, jatom, iatom) has already been calculated! {(iRm, jatom, iatom)}"
|
|
173
|
+
)
|
|
174
|
+
Gij_up = self.GR_atom(Gup[iR], iatom, jatom)
|
|
175
|
+
Gji_dn = self.GR_atom(Gdn[iRm], jatom, iatom)
|
|
164
176
|
tmp = 0.0j
|
|
165
177
|
# t = self.get_Delta(iatom) @ Gij_up @ self.get_Delta(jatom) @ Gji_dn
|
|
166
178
|
t = np.einsum(
|
|
@@ -169,15 +181,78 @@ class ExchangeCL2(ExchangeCL):
|
|
|
169
181
|
np.matmul(self.get_Delta(jatom), Gji_dn),
|
|
170
182
|
)
|
|
171
183
|
tmp = np.sum(t)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
# Use R vectors for keys for I/O compatibility
|
|
185
|
+
R_vec = self.short_Rlist[iR]
|
|
186
|
+
Rm_vec = self.short_Rlist[iRm]
|
|
187
|
+
Jorb_list[(R_vec, iatom, jatom)] = t / (4.0 * np.pi)
|
|
188
|
+
JJ_list[(R_vec, iatom, jatom)] = tmp / (4.0 * np.pi)
|
|
189
|
+
Rij_done.add((iR, iatom, jatom))
|
|
190
|
+
if (iRm, jatom, iatom) not in Rij_done:
|
|
191
|
+
Jorb_list[(Rm_vec, jatom, iatom)] = t.T / (4.0 * np.pi)
|
|
192
|
+
JJ_list[(Rm_vec, jatom, iatom)] = tmp / (4.0 * np.pi)
|
|
193
|
+
Rij_done.add((iRm, jatom, iatom))
|
|
179
194
|
return Jorb_list, JJ_list
|
|
180
195
|
|
|
196
|
+
def get_all_A_vectorized(self, Gup, Gdn):
|
|
197
|
+
"""
|
|
198
|
+
Vectorized calculation of all A matrix elements for collinear exchange.
|
|
199
|
+
Follows the pattern from TB2J/exchange.py get_all_A_vectorized method.
|
|
200
|
+
|
|
201
|
+
:param Gup: Green's function array for spin up, shape (nR, nbasis, nbasis)
|
|
202
|
+
:param Gdn: Green's function array for spin down, shape (nR, nbasis, nbasis)
|
|
203
|
+
:returns: tuple of (Jorb_list, JJ_list) with R vector keys
|
|
204
|
+
"""
|
|
205
|
+
# Get magnetic sites and their orbital indices
|
|
206
|
+
magnetic_sites = self.ind_mag_atoms
|
|
207
|
+
iorbs = [self.iorb(site) for site in magnetic_sites]
|
|
208
|
+
|
|
209
|
+
# Build the Delta matrices for all magnetic sites
|
|
210
|
+
Delta = [self.get_Delta(site) for site in magnetic_sites]
|
|
211
|
+
|
|
212
|
+
# Initialize results dictionaries
|
|
213
|
+
Jorb_list = {}
|
|
214
|
+
JJ_list = {}
|
|
215
|
+
|
|
216
|
+
# Batch compute all exchange tensors following the vectorized approach
|
|
217
|
+
for i, j in product(range(len(magnetic_sites)), repeat=2):
|
|
218
|
+
idx, jdx = iorbs[i], iorbs[j]
|
|
219
|
+
|
|
220
|
+
# Extract Gij and Gji for all R vectors at once
|
|
221
|
+
Gij = Gup[:, idx][:, :, jdx] # Shape: (nR, ni, nj)
|
|
222
|
+
# Since short_Rlist is properly ordered, we can flip Gji along R axis
|
|
223
|
+
# to get Gji(-R) for Gij(R)
|
|
224
|
+
Gji = np.flip(Gdn[:, jdx][:, :, idx], axis=0) # Shape: (nR, nj, ni)
|
|
225
|
+
|
|
226
|
+
# Compute exchange tensors for all R vectors at once
|
|
227
|
+
# Following collinear formula: Delta_i @ Gij @ Delta_j @ Gji
|
|
228
|
+
t_tensor = np.einsum(
|
|
229
|
+
"ab,rbc,cd,rda->rac", Delta[i], Gij, Delta[j], Gji, optimize="optimal"
|
|
230
|
+
) / (4.0 * np.pi)
|
|
231
|
+
tmp_tensor = np.sum(t_tensor, axis=(1, 2)) # Shape: (nR,)
|
|
232
|
+
|
|
233
|
+
mi, mj = (magnetic_sites[i], magnetic_sites[j])
|
|
234
|
+
|
|
235
|
+
# Store results for each R vector
|
|
236
|
+
for iR, R_vec in enumerate(self.short_Rlist):
|
|
237
|
+
# Store with R vector key for compatibility
|
|
238
|
+
Jorb_list[(R_vec, mi, mj)] = t_tensor[iR] # Shape: (ni, nj)
|
|
239
|
+
JJ_list[(R_vec, mi, mj)] = tmp_tensor[iR] # Scalar
|
|
240
|
+
|
|
241
|
+
# Filter results to only include atom pairs within cutoff distance
|
|
242
|
+
# This matches the behavior of the original get_all_A method
|
|
243
|
+
filtered_Jorb_list = {}
|
|
244
|
+
filtered_JJ_list = {}
|
|
245
|
+
|
|
246
|
+
for iR, atom_pairs in self.R_ijatom_dict.items():
|
|
247
|
+
R_vec = self.short_Rlist[iR]
|
|
248
|
+
for iatom, jatom in atom_pairs:
|
|
249
|
+
key = (R_vec, iatom, jatom)
|
|
250
|
+
if key in Jorb_list:
|
|
251
|
+
filtered_Jorb_list[key] = Jorb_list[key]
|
|
252
|
+
filtered_JJ_list[key] = JJ_list[key]
|
|
253
|
+
|
|
254
|
+
return filtered_Jorb_list, filtered_JJ_list
|
|
255
|
+
|
|
181
256
|
def A_to_Jtensor(self):
|
|
182
257
|
for key, val in self.JJ.items():
|
|
183
258
|
# key:(R, iatom, jatom)
|
|
@@ -230,27 +305,182 @@ class ExchangeCL2(ExchangeCL):
|
|
|
230
305
|
# integrate = trapezoidal_nonuniform
|
|
231
306
|
# elif method == "simpson":
|
|
232
307
|
# integrate = simpson_nonuniform
|
|
233
|
-
for
|
|
234
|
-
for iatom, jatom in
|
|
235
|
-
#
|
|
236
|
-
|
|
308
|
+
for iR in self.R_ijatom_dict:
|
|
309
|
+
for iatom, jatom in self.R_ijatom_dict[iR]:
|
|
310
|
+
# Use R vector key consistently
|
|
311
|
+
R_vec = self.short_Rlist[iR]
|
|
312
|
+
key = (R_vec, iatom, jatom)
|
|
313
|
+
# self.Jorb[key] = integrate(
|
|
314
|
+
# self.contour.path, self.Jorb_list[key]
|
|
237
315
|
# )
|
|
238
|
-
# self.JJ[
|
|
239
|
-
# self.contour.path, self.JJ_list[
|
|
316
|
+
# self.JJ[key] = integrate(
|
|
317
|
+
# self.contour.path, self.JJ_list[key]
|
|
240
318
|
# )
|
|
241
|
-
self.Jorb[
|
|
242
|
-
|
|
243
|
-
)
|
|
244
|
-
self.JJ[(R, iatom, jatom)] = self.contour.integrate_values(
|
|
245
|
-
self.JJ_list[(R, iatom, jatom)]
|
|
246
|
-
)
|
|
319
|
+
self.Jorb[key] = self.contour.integrate_values(self.Jorb_list[key])
|
|
320
|
+
self.JJ[key] = self.contour.integrate_values(self.JJ_list[key])
|
|
247
321
|
|
|
248
322
|
def get_quantities_per_e(self, e):
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
323
|
+
# short_Rlist now contains actual R vectors
|
|
324
|
+
GR_up = self.Gup.get_GR(self.short_Rlist, energy=e)
|
|
325
|
+
GR_dn = self.Gdn.get_GR(self.short_Rlist, energy=e)
|
|
326
|
+
|
|
327
|
+
# Save diagonal elements of Green's functions for charge and magnetic moment calculation
|
|
328
|
+
# Only if debug option is enabled
|
|
329
|
+
if self.debug_options.get("compute_charge_moments", False):
|
|
330
|
+
self.save_greens_function_diagonals_collinear(GR_up, GR_dn, e)
|
|
331
|
+
|
|
332
|
+
# Use vectorized method with fallback to original method
|
|
333
|
+
try:
|
|
334
|
+
Jorb_list, JJ_list = self.get_all_A_vectorized(GR_up, GR_dn)
|
|
335
|
+
# Jorb_list, JJ_list = self.get_all_A(GR_up, GR_dn)
|
|
336
|
+
except Exception as ex:
|
|
337
|
+
print(f"Vectorized method failed: {ex}, falling back to original method")
|
|
338
|
+
Jorb_list, JJ_list = self.get_all_A(GR_up, GR_dn)
|
|
252
339
|
return dict(Jorb_list=Jorb_list, JJ_list=JJ_list)
|
|
253
340
|
|
|
341
|
+
def save_greens_function_diagonals_collinear(self, GR_up, GR_dn, energy):
|
|
342
|
+
"""
|
|
343
|
+
Save diagonal elements of spin-resolved Green's functions for each atom.
|
|
344
|
+
These will be used to compute charge and magnetic moments in collinear case.
|
|
345
|
+
|
|
346
|
+
:param GR_up: Spin-up Green's function array of shape (nR, nbasis, nbasis)
|
|
347
|
+
:param GR_dn: Spin-down Green's function array of shape (nR, nbasis, nbasis)
|
|
348
|
+
:param energy: Current energy value
|
|
349
|
+
"""
|
|
350
|
+
# For proper charge and magnetic moment calculation, we need to sum over k-points
|
|
351
|
+
# with weights: Σ_k S(k)·G(k)·w(k)
|
|
352
|
+
|
|
353
|
+
# Initialize the k-summed SG matrices for this energy
|
|
354
|
+
nbasis = GR_up.shape[1]
|
|
355
|
+
SG_up_ksum = np.zeros((nbasis, nbasis), dtype=complex)
|
|
356
|
+
SG_dn_ksum = np.zeros((nbasis, nbasis), dtype=complex)
|
|
357
|
+
|
|
358
|
+
# Get k-points and weights from Green's function object
|
|
359
|
+
kpts = self.Gup.kpts
|
|
360
|
+
kweights = self.Gup.kweights
|
|
361
|
+
|
|
362
|
+
# Use the passed energy parameter
|
|
363
|
+
current_energy = energy
|
|
364
|
+
|
|
365
|
+
# Sum over all k-points
|
|
366
|
+
for ik, kpt in enumerate(kpts):
|
|
367
|
+
# Get G(k) for current energy
|
|
368
|
+
Gk_up = self.Gup.get_Gk(ik, energy=current_energy)
|
|
369
|
+
Gk_dn = self.Gdn.get_Gk(ik, energy=current_energy)
|
|
370
|
+
|
|
371
|
+
if not self.Gup.is_orthogonal:
|
|
372
|
+
Sk = self.Gup.get_Sk(ik) # Same overlap for both spins
|
|
373
|
+
SG_up_ksum += Sk @ Gk_up * kweights[ik]
|
|
374
|
+
SG_dn_ksum += Sk @ Gk_dn * kweights[ik]
|
|
375
|
+
else:
|
|
376
|
+
# For orthogonal case, S is identity
|
|
377
|
+
SG_up_ksum += Gk_up * kweights[ik]
|
|
378
|
+
SG_dn_ksum += Gk_dn * kweights[ik]
|
|
379
|
+
|
|
380
|
+
# Store both spin channels
|
|
381
|
+
if not hasattr(self, "G_diagonal_up"):
|
|
382
|
+
self.G_diagonal_up = {iatom: [] for iatom in range(len(self.atoms))}
|
|
383
|
+
self.G_diagonal_dn = {iatom: [] for iatom in range(len(self.atoms))}
|
|
384
|
+
|
|
385
|
+
for iatom in self.orb_dict:
|
|
386
|
+
# Get orbital indices for this atom
|
|
387
|
+
orbi = self.iorb(iatom)
|
|
388
|
+
# Extract diagonal elements for this atom
|
|
389
|
+
G_up_diag = np.diag(SG_up_ksum[np.ix_(orbi, orbi)])
|
|
390
|
+
G_dn_diag = np.diag(SG_dn_ksum[np.ix_(orbi, orbi)])
|
|
391
|
+
|
|
392
|
+
self.G_diagonal_up[iatom].append(G_up_diag)
|
|
393
|
+
self.G_diagonal_dn[iatom].append(G_dn_diag)
|
|
394
|
+
|
|
395
|
+
def compute_charge_and_magnetic_moments(self):
|
|
396
|
+
"""
|
|
397
|
+
Compute charge and magnetic moments from stored spin-resolved Green's function diagonals.
|
|
398
|
+
Uses the relation for collinear case:
|
|
399
|
+
- Charge: n_i = -1/π ∫ (Im[Tr(S·G_ii^↑(E))] + Im[Tr(S·G_ii^↓(E))]) dE
|
|
400
|
+
- Magnetic moment: m_i = -1/π ∫ (Im[Tr(S·G_ii^↑(E))] - Im[Tr(S·G_ii^↓(E))]) dE
|
|
401
|
+
where S is the overlap matrix.
|
|
402
|
+
"""
|
|
403
|
+
# Only run if debug option is enabled
|
|
404
|
+
if not self.debug_options.get("compute_charge_moments", False):
|
|
405
|
+
# Just use density matrix method directly
|
|
406
|
+
self.get_rho_atom()
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
if not hasattr(self, "G_diagonal_up") or not self.G_diagonal_up:
|
|
410
|
+
print(
|
|
411
|
+
"Warning: No Green's function diagonals stored. Cannot compute charge and magnetic moments."
|
|
412
|
+
)
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
self.charges = np.zeros(len(self.atoms))
|
|
416
|
+
self.spinat = np.zeros((len(self.atoms), 3))
|
|
417
|
+
|
|
418
|
+
# Arrays to store Green's function results for comparison
|
|
419
|
+
gf_charges = np.zeros(len(self.atoms))
|
|
420
|
+
gf_spinat = np.zeros((len(self.atoms), 3))
|
|
421
|
+
|
|
422
|
+
for iatom in range(len(self.atoms)):
|
|
423
|
+
if not self.G_diagonal_up[iatom] or not self.G_diagonal_dn[iatom]:
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
# Stack all diagonal elements for this atom
|
|
427
|
+
G_up_diags = np.array(
|
|
428
|
+
self.G_diagonal_up[iatom]
|
|
429
|
+
) # shape: (n_energies, n_orbitals)
|
|
430
|
+
G_dn_diags = np.array(
|
|
431
|
+
self.G_diagonal_dn[iatom]
|
|
432
|
+
) # shape: (n_energies, n_orbitals)
|
|
433
|
+
|
|
434
|
+
# Integrate over energy using the same contour as exchange calculation
|
|
435
|
+
# Charge: -1/π ∫ Im[G_up + G_dn] dE
|
|
436
|
+
integrated_up = -np.imag(self.contour.integrate_values(G_up_diags)) / np.pi
|
|
437
|
+
integrated_dn = -np.imag(self.contour.integrate_values(G_dn_diags)) / np.pi
|
|
438
|
+
|
|
439
|
+
# Sum over orbitals and spin channels to get total charge
|
|
440
|
+
gf_charge = np.sum(integrated_up) + np.sum(integrated_dn)
|
|
441
|
+
|
|
442
|
+
# Magnetic moment (assuming z-direction for collinear case)
|
|
443
|
+
# m_z = -1/π ∫ Im[G_up - G_dn] dE
|
|
444
|
+
gf_spin_z = np.sum(integrated_up) - np.sum(integrated_dn)
|
|
445
|
+
|
|
446
|
+
# Store Green's function results
|
|
447
|
+
gf_charges[iatom] = gf_charge
|
|
448
|
+
gf_spinat[iatom, 2] = gf_spin_z
|
|
449
|
+
|
|
450
|
+
# Compute using density matrix method for comparison
|
|
451
|
+
self.get_rho_atom() # This computes charges and spinat using density matrix
|
|
452
|
+
dm_charge = self.charges[iatom]
|
|
453
|
+
dm_spin_z = self.spinat[iatom, 2]
|
|
454
|
+
|
|
455
|
+
# Compare methods if difference is above threshold
|
|
456
|
+
charge_diff = abs(gf_charge - dm_charge)
|
|
457
|
+
spin_diff = abs(gf_spin_z - dm_spin_z)
|
|
458
|
+
threshold = self.debug_options.get("charge_moment_threshold", 1e-4)
|
|
459
|
+
|
|
460
|
+
if charge_diff > threshold or spin_diff > threshold:
|
|
461
|
+
print(f"Atom {iatom}:")
|
|
462
|
+
print(f" Green's function charge: {gf_charge:.6f}")
|
|
463
|
+
print(f" Density matrix charge: {dm_charge:.6f}")
|
|
464
|
+
if charge_diff > threshold:
|
|
465
|
+
print(
|
|
466
|
+
f" Charge difference: {charge_diff:.6f} (threshold: {threshold})"
|
|
467
|
+
)
|
|
468
|
+
print(f" Green's function magnetic moment (z): {gf_spin_z:.6f}")
|
|
469
|
+
print(f" Density matrix magnetic moment (z): {dm_spin_z:.6f}")
|
|
470
|
+
if spin_diff > threshold:
|
|
471
|
+
print(
|
|
472
|
+
f" Spin difference: {spin_diff:.6f} (threshold: {threshold})"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# By default, use density matrix output unless debug option says otherwise
|
|
476
|
+
if not self.debug_options.get("use_density_matrix_output", True):
|
|
477
|
+
# Override with Green's function values (not recommended)
|
|
478
|
+
self.charges[iatom] = gf_charge
|
|
479
|
+
self.spinat[iatom, 2] = gf_spin_z
|
|
480
|
+
|
|
481
|
+
# Restore the final values from density matrix method
|
|
482
|
+
self.get_rho_atom()
|
|
483
|
+
|
|
254
484
|
def calculate_all(self):
|
|
255
485
|
"""
|
|
256
486
|
The top level.
|
|
@@ -271,13 +501,19 @@ class ExchangeCL2(ExchangeCL):
|
|
|
271
501
|
for i, result in enumerate(results):
|
|
272
502
|
Jorb_list = result["Jorb_list"]
|
|
273
503
|
JJ_list = result["JJ_list"]
|
|
274
|
-
for iR
|
|
275
|
-
for iatom, jatom in self.R_ijatom_dict[
|
|
276
|
-
|
|
504
|
+
for iR in self.R_ijatom_dict:
|
|
505
|
+
for iatom, jatom in self.R_ijatom_dict[iR]:
|
|
506
|
+
# Use R vector key consistently across all dictionaries
|
|
507
|
+
R_vec = self.short_Rlist[iR]
|
|
508
|
+
key = (R_vec, iatom, jatom)
|
|
277
509
|
self.Jorb_list[key].append(Jorb_list[key])
|
|
278
510
|
self.JJ_list[key].append(JJ_list[key])
|
|
279
511
|
self.integrate()
|
|
280
512
|
self.get_rho_atom()
|
|
513
|
+
|
|
514
|
+
# Compute charge and magnetic moments from Green's function diagonals
|
|
515
|
+
self.compute_charge_and_magnetic_moments()
|
|
516
|
+
|
|
281
517
|
self.A_to_Jtensor()
|
|
282
518
|
|
|
283
519
|
def write_output(self, path="TB2J_results"):
|
TB2J/exchange_params.py
CHANGED
|
@@ -33,6 +33,12 @@ class ExchangeParams:
|
|
|
33
33
|
mae_angles = None
|
|
34
34
|
orth = False
|
|
35
35
|
ibz = False
|
|
36
|
+
# Debug options
|
|
37
|
+
debug_options = {
|
|
38
|
+
"compute_charge_moments": False, # Whether to compute charge and magnetic moments with Green's function method
|
|
39
|
+
"use_density_matrix_output": True, # Whether to use density matrix results as default output
|
|
40
|
+
"charge_moment_threshold": 1e-4, # Threshold for printing comparison when difference > threshold
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
def __init__(
|
|
38
44
|
self,
|
|
@@ -57,6 +63,7 @@ class ExchangeParams:
|
|
|
57
63
|
orth=False,
|
|
58
64
|
ibz=False,
|
|
59
65
|
index_magnetic_atoms=None,
|
|
66
|
+
debug_options=None,
|
|
60
67
|
):
|
|
61
68
|
self.efermi = efermi
|
|
62
69
|
self.basis = basis
|
|
@@ -83,6 +90,23 @@ class ExchangeParams:
|
|
|
83
90
|
self.ibz = ibz
|
|
84
91
|
self.index_magnetic_atoms = index_magnetic_atoms
|
|
85
92
|
|
|
93
|
+
# Initialize debug options
|
|
94
|
+
if debug_options is None:
|
|
95
|
+
self.debug_options = {
|
|
96
|
+
"compute_charge_moments": False,
|
|
97
|
+
"use_density_matrix_output": True,
|
|
98
|
+
"charge_moment_threshold": 1e-4,
|
|
99
|
+
}
|
|
100
|
+
else:
|
|
101
|
+
# Update default debug options with user-provided ones
|
|
102
|
+
default_debug = {
|
|
103
|
+
"compute_charge_moments": False,
|
|
104
|
+
"use_density_matrix_output": True,
|
|
105
|
+
"charge_moment_threshold": 1e-4,
|
|
106
|
+
}
|
|
107
|
+
default_debug.update(debug_options)
|
|
108
|
+
self.debug_options = default_debug
|
|
109
|
+
|
|
86
110
|
def set_params(self, **kwargs):
|
|
87
111
|
for key, val in kwargs.items():
|
|
88
112
|
setattr(self, key, val)
|
TB2J/green.py
CHANGED
|
@@ -56,13 +56,26 @@ def find_energy_ingap(evals, rbound, gap=2.0):
|
|
|
56
56
|
find a energy inside a gap below rbound (right bound),
|
|
57
57
|
return the energy gap top - 0.5.
|
|
58
58
|
"""
|
|
59
|
+
# print("Finding energy in gap...")
|
|
60
|
+
# print(f"Right bound: {rbound}, min gap size: {gap}")
|
|
59
61
|
m0 = np.sort(evals.flatten())
|
|
60
|
-
|
|
62
|
+
# print(f"Min eigenvalue: {m0[0]}, Max eigenvalue: {m0[-1]}")
|
|
63
|
+
m = m0[m0 <= rbound]
|
|
64
|
+
# append the next state above rbound to m
|
|
65
|
+
if len(m0) > len(m):
|
|
66
|
+
m = np.append(m, m0[len(m)])
|
|
67
|
+
# print(f"Number of states below right bound: {len(m)}")
|
|
68
|
+
# print(f"Max eigenvalue below right bound: {m[-1]}")
|
|
61
69
|
ind = np.where(np.diff(m) > gap)[0]
|
|
70
|
+
# print(f"Number of gaps found: {len(ind)}")
|
|
71
|
+
# print("ind[-1]: ", ind[-1] if len(ind) > 0 else "N/A")
|
|
72
|
+
emin = 0.0
|
|
62
73
|
if len(ind) == 0:
|
|
63
|
-
|
|
74
|
+
emin = m0[0] - 0.5
|
|
64
75
|
else:
|
|
65
|
-
|
|
76
|
+
emin = m[ind[-1] + 1] - 0.5
|
|
77
|
+
# print("emin:", emin)
|
|
78
|
+
return emin
|
|
66
79
|
|
|
67
80
|
|
|
68
81
|
class TBGreen:
|
|
@@ -79,12 +92,22 @@ class TBGreen:
|
|
|
79
92
|
use_cache=False,
|
|
80
93
|
cache_path=None,
|
|
81
94
|
nproc=1,
|
|
95
|
+
initial_emin=-25,
|
|
82
96
|
):
|
|
83
97
|
"""
|
|
84
98
|
:param tbmodel: A tight binding model
|
|
85
99
|
:param kmesh: size of monkhorst pack. e.g [6,6,6]
|
|
86
100
|
:param efermi: fermi energy.
|
|
101
|
+
:param gamma: whether to include gamma point in monkhorst pack
|
|
102
|
+
:param kpts: user defined kpoints
|
|
103
|
+
:param kweights: weights for user defined kpoints
|
|
104
|
+
:param k_sym: whether the kpoints are symmetrized
|
|
105
|
+
:param use_cache: whether to use cache to store wavefunctions
|
|
106
|
+
:param cache_path: path to store cache
|
|
107
|
+
:param nproc: number of processes to use
|
|
108
|
+
:param emin: minimum energy relative to fermi level to consider
|
|
87
109
|
"""
|
|
110
|
+
self.initial_emin = initial_emin
|
|
88
111
|
self.tbmodel = tbmodel
|
|
89
112
|
self.is_orthogonal = tbmodel.is_orthogonal
|
|
90
113
|
self.R2kfactor = tbmodel.R2kfactor
|
|
@@ -235,9 +258,21 @@ class TBGreen:
|
|
|
235
258
|
self.efermi = occ.efermi(copy.deepcopy(self.evals))
|
|
236
259
|
print(f"Fermi energy found: {self.efermi}")
|
|
237
260
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
261
|
+
self.adjusted_emin = (
|
|
262
|
+
find_energy_ingap(
|
|
263
|
+
self.evals, rbound=self.efermi + self.initial_emin, gap=2.0
|
|
264
|
+
)
|
|
265
|
+
- self.efermi
|
|
266
|
+
)
|
|
267
|
+
# print(f"Adjusted emin relative to Fermi level: {self.adjusted_emin}")
|
|
268
|
+
self.evals, self.evecs = self._reduce_eigens(
|
|
269
|
+
self.evals,
|
|
270
|
+
self.evecs,
|
|
271
|
+
emin=self.efermi + self.adjusted_emin,
|
|
272
|
+
emax=self.efermi + 5.1,
|
|
273
|
+
# emin=self.efermi -10,
|
|
274
|
+
# emax=self.efermi + 10,
|
|
275
|
+
)
|
|
241
276
|
if self._use_cache:
|
|
242
277
|
evecs = self.evecs
|
|
243
278
|
self.evecs_shape = self.evecs.shape
|
|
@@ -376,38 +411,28 @@ class TBGreen:
|
|
|
376
411
|
Gk_all[ik] = self.get_Gk(ik, energy)
|
|
377
412
|
return Gk_all
|
|
378
413
|
|
|
379
|
-
def
|
|
414
|
+
def compute_GR(self, Rpts, kpts, Gks):
|
|
415
|
+
Rvecs = np.array(Rpts)
|
|
416
|
+
phase = np.exp(self.k2Rfactor * np.einsum("ni,mi->nm", Rvecs, kpts))
|
|
417
|
+
phase *= self.kweights[None]
|
|
418
|
+
GR = np.einsum("kij,rk->rij", Gks, phase, optimize="optimal")
|
|
419
|
+
return GR
|
|
420
|
+
|
|
421
|
+
def get_GR(self, Rpts, energy, Gk_all=None):
|
|
380
422
|
"""calculate real space Green's function for one energy, all R points.
|
|
381
423
|
G(R, epsilon) = G(k, epsilon) exp(-2\pi i R.dot. k)
|
|
382
424
|
:param Rpts: R points
|
|
383
|
-
:param energy:
|
|
384
|
-
:
|
|
385
|
-
:
|
|
425
|
+
:param energy: energy value
|
|
426
|
+
:param Gk_all: optional pre-computed Gk for all k-points
|
|
427
|
+
:returns: real space green's function for one energy for a list of R.
|
|
428
|
+
:rtype: numpy array of shape (len(Rpts), nbasis, nbasis)
|
|
386
429
|
"""
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
rhoR = defaultdict(lambda: 0.0j)
|
|
390
|
-
for ik, kpt in enumerate(self.kpts):
|
|
391
|
-
if Gk_all is None:
|
|
392
|
-
Gk = self.get_Gk(ik, energy)
|
|
393
|
-
else:
|
|
394
|
-
Gk = Gk_all[ik]
|
|
395
|
-
if get_rho:
|
|
396
|
-
if self.is_orthogonal:
|
|
397
|
-
rhok = Gk
|
|
398
|
-
else:
|
|
399
|
-
rhok = self.get_Sk(ik) @ Gk
|
|
400
|
-
for iR, R in enumerate(Rpts):
|
|
401
|
-
phase = np.exp(self.k2Rfactor * np.dot(R, kpt))
|
|
402
|
-
tmp = Gk * (phase * self.kweights[ik])
|
|
403
|
-
GR[R] += tmp
|
|
404
|
-
# change this if need full rho
|
|
405
|
-
if get_rho and R == (0, 0, 0):
|
|
406
|
-
rhoR[R] += rhok * (phase * self.kweights[ik])
|
|
407
|
-
if get_rho:
|
|
408
|
-
return GR, rhoR
|
|
430
|
+
if Gk_all is None:
|
|
431
|
+
Gks = self.get_Gk_all(energy)
|
|
409
432
|
else:
|
|
410
|
-
|
|
433
|
+
Gks = Gk_all
|
|
434
|
+
|
|
435
|
+
return self.compute_GR(Rpts, self.kpts, Gks)
|
|
411
436
|
|
|
412
437
|
def get_GR_and_dGRdx1(self, Rpts, energy, dHdx):
|
|
413
438
|
"""
|
TB2J/myTB.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import numpy as np
|
|
3
1
|
import copy
|
|
4
|
-
|
|
5
|
-
from scipy.sparse import csr_matrix
|
|
6
|
-
from scipy.io import netcdf_file
|
|
2
|
+
import os
|
|
7
3
|
from collections import defaultdict
|
|
8
4
|
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
9
7
|
# from tbmodels import Model
|
|
10
8
|
from ase.atoms import Atoms
|
|
9
|
+
from scipy.io import netcdf_file
|
|
10
|
+
from scipy.linalg import eigh
|
|
11
|
+
from scipy.sparse import csr_matrix
|
|
12
|
+
|
|
11
13
|
from TB2J.utils import auto_assign_basis_name
|
|
12
|
-
from TB2J.wannier import
|
|
14
|
+
from TB2J.wannier import parse_atoms, parse_ham, parse_tb, parse_xyz
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class AbstractTB:
|
|
@@ -187,9 +189,7 @@ class MyTB(AbstractTB):
|
|
|
187
189
|
if os.path.exists(tb_fname):
|
|
188
190
|
xcart, nbasis, data, R_degens = parse_tb(fname=tb_fname)
|
|
189
191
|
else:
|
|
190
|
-
nbasis, data, R_degens = parse_ham(
|
|
191
|
-
fname=os.path.join(path, prefix + "_hr.dat")
|
|
192
|
-
)
|
|
192
|
+
nbasis, data, R_degens = parse_ham(fname=hr_fname)
|
|
193
193
|
xcart, _, _ = parse_xyz(fname=os.path.join(path, prefix + "_centres.xyz"))
|
|
194
194
|
|
|
195
195
|
if atoms is None:
|
|
@@ -237,7 +237,7 @@ class MyTB(AbstractTB):
|
|
|
237
237
|
return m
|
|
238
238
|
|
|
239
239
|
def gen_ham(self, k, convention=2):
|
|
240
|
-
"""
|
|
240
|
+
r"""
|
|
241
241
|
generate hamiltonian matrix at k point.
|
|
242
242
|
H_k( i, j)=\sum_R H_R(i, j)^phase.
|
|
243
243
|
There are two conventions,
|
|
@@ -451,7 +451,7 @@ class MyTB(AbstractTB):
|
|
|
451
451
|
nbasis = root.dimensions["nbasis"]
|
|
452
452
|
nspin = root.dimensions["nspin"]
|
|
453
453
|
ndim = root.dimensions["ndim"]
|
|
454
|
-
|
|
454
|
+
_natom = root.dimensions["natom"]
|
|
455
455
|
Rlist = root.variables["R"][:]
|
|
456
456
|
mdata_real = root.variables["data_real"][:]
|
|
457
457
|
mdata_imag = root.variables["data_imag"][:]
|