class ExchangeCL2(ExchangeCL): def set_tbmodels(self, tbmodels): """ only difference is a colinear tag. """ self.tbmodel_up, self.tbmodel_dn = tbmodels self.Gup = TBGreen(self.tbmodel_up, self.kmesh, self.efermi, use_cache=self._use_cache, cache_path='TB2J_results/cache/spinup') self.Gdn = TBGreen(self.tbmodel_dn, self.kmesh, self.efermi, use_cache=self._use_cache, cache_path='TB2J_results/cache/spindn') self.norb = self.Gup.norb self.nbasis = self.Gup.nbasis + self.Gdn.nbasis self.rho_up = np.zeros((self.norb, self.norb), dtype=float) self.rho_dn = np.zeros((self.norb, self.norb), dtype=float) self.JJ = defaultdict(lambda: 0.0j) self.Jorb = defaultdict(lambda: 0.0j) self.HR0_up = self.Gup.H0 self.HR0_dn = self.Gdn.H0 self.Delta = self.HR0_up - self.HR0_dn if self.Gup.is_orthogonal and self.Gdn.is_orthogonal: self.is_orthogonal = True else: self.is_orthogonal = False #self.S0=self.Gup.S0 self._is_colinear = True self.exchange_Jdict = {} self.exchange_Jdict_orb = {} self.biquadratic = False def get_Delta(self, iatom): orbs = self.iorb(iatom) return self.Delta[np.ix_(orbs, orbs)] def GR_atom(self, GR, iatom, jatom): """Given a green's function matrix, return the [iatom, jatom] component. :param GR: Green's function matrix :param iatom: index of atom i :param jatom: index of atom j :returns: G_ij :rtype: complex matrix. """ orbi = self.iorb(iatom) orbj = self.iorb(jatom) return GR[np.ix_(orbi, orbj)] def get_A_ijR(self, Gup, Gdn, iatom, jatom, de): Rij_done = set() for R, ijpairs in self.R_ijatom_dict.items(): if (iatom, jatom) in ijpairs and (R, iatom, jatom) not in Rij_done: Gij_up = self.GR_atom(Gup[R], iatom, jatom) Rm = tuple(-x for x in R) Gji_dn = self.GR_atom(Gdn[Rm], jatom, iatom) tmp = 0.0j #t = self.get_Delta(iatom) @ Gij_up @ self.get_Delta(jatom) @ Gji_dn t = np.einsum('ij, ji-> ij', np.matmul(self.get_Delta(iatom), Gij_up), np.matmul(self.get_Delta(jatom), Gji_dn)) if self.biquadratic: A = np.einsum('ij, ji-> ij', np.matmul(self.get_Delta(iatom), Gij_up), np.matmul(self.get_Delta(jatom), Gji_up)) C = np.einsum('ij, ji-> ij', np.matmul(self.get_Delta(iatom), Gij_down), np.matmul(self.get_Delta(jatom), Gji_down)) tmp = np.sum(t) self.Jorb[(R, iatom, jatom)] += t * de / (4.0 * np.pi) self.JJ[(R, iatom, jatom)] += tmp * de / (4.0 * np.pi) Rij_done.add((R, iatom, jatom)) if (Rm, jatom, iatom) not in Rij_done: self.Jorb[(Rm, jatom, iatom)] += t * de / (4.0 * np.pi) self.JJ[(Rm, jatom, iatom)] += tmp * de / (4.0 * np.pi) Rij_done.add((Rm, jatom, iatom)) def get_all_A(self, Gup, Gdn, de): """ Calculate all A matrix elements Loop over all magnetic atoms. :param G: Green's function. :param de: energy step. """ Rij_done = set() for R, ijpairs in self.R_ijatom_dict.items(): for iatom, jatom in ijpairs: if (R, iatom, jatom) not in Rij_done: Rm = tuple(-x for x in R) if (Rm, jatom, iatom) in Rij_done: raise KeyError( f"Strange (Rm, jatom, iatom) has already been calculated! {(Rm, jatom, iatom)}" ) Gij_up = self.GR_atom(Gup[R], iatom, jatom) Gji_dn = self.GR_atom(Gdn[Rm], jatom, iatom) tmp = 0.0j #t = self.get_Delta(iatom) @ Gij_up @ self.get_Delta(jatom) @ Gji_dn t = np.einsum('ij, ji-> ij', np.matmul(self.get_Delta(iatom), Gij_up), np.matmul(self.get_Delta(jatom), Gji_dn)) tmp = np.sum(t) self.Jorb[(R, iatom, jatom)] += t * de / (4.0 * np.pi) self.JJ[(R, iatom, jatom)] += tmp * de / (4.0 * np.pi) Rij_done.add((R, iatom, jatom)) if (Rm, jatom, iatom) not in Rij_done: self.Jorb[(Rm, jatom, iatom)] += t * de / (4.0 * np.pi) self.JJ[(Rm, jatom, iatom)] += tmp * de / (4.0 * np.pi) Rij_done.add((Rm, jatom, iatom)) def A_to_Jtensor(self): for key, val in self.JJ.items(): # key:(R, iatom, jatom) R, iatom, jatom = key ispin = self.ispin(iatom) jspin = self.ispin(jatom) keyspin = (R, ispin, jspin) is_nonself = not (R == (0, 0, 0) and iatom == jatom) Jij = np.imag(val) / np.sign( np.dot(self.spinat[iatom], self.spinat[jatom])) Jorbij = np.imag(self.Jorb[key]) / np.sign( np.dot(self.spinat[iatom], self.spinat[jatom])) if is_nonself: self.exchange_Jdict[keyspin] = Jij self.exchange_Jdict_orb[keyspin] = Jorbij def get_rho_e(self, rho_up, rho_dn, de): #GR0_up = GR_up[(0, 0, 0)] #GR0_dn = GR_dn[(0, 0, 0)] #if self.is_orthogonal: # self.rho_up += -1.0 / np.pi * np.imag(GR0_up * de) # self.rho_dn += -1.0 / np.pi * np.imag(GR0_dn * de) #else: # self.rho_up += -1.0 / np.pi * np.imag(self.S0@GR0_up * de) # self.rho_dn += -1.0 / np.pi * np.imag(self.S0@GR0_dn * de) self.rho_up += -1.0 / np.pi * np.imag(rho_up[(0, 0, 0)] * de) self.rho_dn += -1.0 / np.pi * np.imag(rho_dn[(0, 0, 0)] * de) def get_rho_atom(self): """ charges and spins from density matrices """ self.charges = np.zeros(len(self.atoms), dtype=float) self.spinat = np.zeros((len(self.atoms), 3), dtype=float) for iatom in self.orb_dict: iorb = self.iorb(iatom) tup = np.real(np.trace(self.rho_up[np.ix_(iorb, iorb)])) tdn = np.real(np.trace(self.rho_dn[np.ix_(iorb, iorb)])) # *2 because there is a 1/2 in the paui_block_all function self.charges[iatom] = tup + tdn self.spinat[iatom, 2] = tup - tdn def finalize(self): self.Gup.clean_cache() self.Gdn.clean_cache() path = 'TB2J_results/cache' if os.path.exists(path): shutil.rmtree(path) def calculate_all(self): """ The top level. """ print("Green's function Calculation started.") widgets = [ ' [', progressbar.Timer(), '] ', progressbar.Bar(), ' (', progressbar.ETA(), ') ', ] bar = progressbar.ProgressBar(maxval=self.contour.npoints, widgets=widgets) bar.start() for ie in range(self.contour.npoints): bar.update(ie) e = self.contour.path[ie] de = self.contour.de[ie] GR_up, rho_up = self.Gup.get_GR(self.short_Rlist, energy=e, get_rho=True) GR_dn, rho_dn = self.Gdn.get_GR(self.short_Rlist, energy=e, get_rho=True) self.get_rho_e(rho_up, rho_dn, de) self.get_all_A(GR_up, GR_dn, de) self.get_rho_atom() self.A_to_Jtensor() bar.finish() def write_output(self, path='TB2J_results'): self._prepare_index_spin() output = SpinIO( atoms=self.atoms, charges=self.charges, spinat=self.spinat, index_spin=self.index_spin, colinear=True, distance_dict=self.distance_dict, exchange_Jdict=self.exchange_Jdict, exchange_Jdict_orb=self.exchange_Jdict_orb, dmi_ddict=None, NJT_Jdict=None, NJT_ddict=None, Jani_dict=None, biquadratic_Jdict=None, description=self.description, ) output.write_all()
class ExchangeCL2(ExchangeCL): def set_tbmodels(self, tbmodels): """ only difference is a colinear tag. """ self.tbmodel_up, self.tbmodel_dn = tbmodels self.Gup = TBGreen(self.tbmodel_up, self.kmesh, self.efermi, use_cache=self._use_cache, cache_path='TB2J_results/cache/spinup', nproc=self.np) self.Gdn = TBGreen(self.tbmodel_dn, self.kmesh, self.efermi, use_cache=self._use_cache, cache_path='TB2J_results/cache/spindn', nproc=self.np) self.norb = self.Gup.norb self.nbasis = self.Gup.nbasis + self.Gdn.nbasis self.rho_up_list = [] self.rho_dn_list = [] self.rho_up = np.zeros((self.norb, self.norb), dtype=float) self.rho_dn = np.zeros((self.norb, self.norb), dtype=float) self.Jorb_list = defaultdict(lambda: []) self.JJ_list = defaultdict(lambda: []) self.JJ = defaultdict(lambda: 0.0j) self.Jorb = defaultdict(lambda: 0.0j) self.HR0_up = self.Gup.H0 self.HR0_dn = self.Gdn.H0 self.Delta = self.HR0_up - self.HR0_dn if self.Gup.is_orthogonal and self.Gdn.is_orthogonal: self.is_orthogonal = True else: self.is_orthogonal = False #self.S0=self.Gup.S0 self._is_colinear = True self.exchange_Jdict = {} self.exchange_Jdict_orb = {} self.biquadratic = False def _clean_tbmodels(self): del self.tbmodel_up del self.tbmodel_dn del self.Gup.tbmodel del self.Gdn.tbmodel def _adjust_emin(self): emin_up = self.Gup.find_energy_ingap(rbound=self.efermi - 5.0) - self.efermi emin_dn = self.Gdn.find_energy_ingap(rbound=self.efermi - 5.0) - self.efermi self.emin = min(emin_up, emin_dn) print(f"A gap is found at {self.emin}, set emin to it.") def get_Delta(self, iatom): #orbs = self.iorb(iatom) #return self.Delta[np.ix_(orbs, orbs)] s = self.orb_slice[iatom] return self.Delta[s, s] def GR_atom(self, GR, iatom, jatom): """Given a green's function matrix, return the [iatom, jatom] component. :param GR: Green's function matrix :param iatom: index of atom i :param jatom: index of atom j :returns: G_ij :rtype: complex matrix. """ #orbi = self.iorb(iatom) #orbj = self.iorb(jatom) #return GR[np.ix_(orbi, orbj)] return GR[self.orb_slice[iatom], self.orb_slice[jatom]] def get_A_ijR(self, Gup, Gdn, iatom, jatom): Rij_done = set() Jorb_list = dict() JJ_list = dict() for R, ijpairs in self.R_ijatom_dict.items(): if (iatom, jatom) in ijpairs and (R, iatom, jatom) not in Rij_done: Gij_up = self.GR_atom(Gup[R], iatom, jatom) Rm = tuple(-x for x in R) Gji_dn = self.GR_atom(Gdn[Rm], jatom, iatom) tmp = 0.0j Deltai = self.get_Delta(iatom) Deltaj = self.get_Delta(jatom) t = np.einsum('ij, ji-> ij', np.matmul(Deltai, Gij_up), np.matmul(Deltaj, Gji_dn)) if self.biquadratic: A = np.einsum('ij, ji-> ij', np.matmul(Deltai, Gij_up), np.matmul(Deltaj, Gji_up)) C = np.einsum('ij, ji-> ij', np.matmul(Deltai, Gij_down), np.matmul(Deltaj, Gji_down)) tmp = np.sum(t) self.Jorb_list[(R, iatom, jatom)].append(t / (4.0 * np.pi)) self.JJ_list[(R, iatom, jatom)].append(tmp / (4.0 * np.pi)) Rij_done.add((R, iatom, jatom)) if (Rm, jatom, iatom) not in Rij_done: Jorb_list[(Rm, jatom, iatom)] = t / (4.0 * np.pi) JJ_list[(Rm, jatom, iatom)] = tmp / (4.0 * np.pi) Rij_done.add((Rm, jatom, iatom)) return Jorb_list, JJ_list def get_all_A(self, Gup, Gdn): """ Calculate all A matrix elements Loop over all magnetic atoms. :param G: Green's function. :param de: energy step. """ Rij_done = set() Jorb_list = dict() JJ_list = dict() for R, ijpairs in self.R_ijatom_dict.items(): for iatom, jatom in ijpairs: if (R, iatom, jatom) not in Rij_done: Rm = tuple(-x for x in R) if (Rm, jatom, iatom) in Rij_done: raise KeyError( f"Strange (Rm, jatom, iatom) has already been calculated! {(Rm, jatom, iatom)}" ) Gij_up = self.GR_atom(Gup[R], iatom, jatom) Gji_dn = self.GR_atom(Gdn[Rm], jatom, iatom) tmp = 0.0j #t = self.get_Delta(iatom) @ Gij_up @ self.get_Delta(jatom) @ Gji_dn t = np.einsum('ij, ji-> ij', np.matmul(self.get_Delta(iatom), Gij_up), np.matmul(self.get_Delta(jatom), Gji_dn)) tmp = np.sum(t) Jorb_list[(R, iatom, jatom)] = t / (4.0 * np.pi) JJ_list[(R, iatom, jatom)] = tmp / (4.0 * np.pi) Rij_done.add((R, iatom, jatom)) if (Rm, jatom, iatom) not in Rij_done: Jorb_list[(Rm, jatom, iatom)] = t / (4.0 * np.pi) JJ_list[(Rm, jatom, iatom)] = tmp / (4.0 * np.pi) Rij_done.add((Rm, jatom, iatom)) return Jorb_list, JJ_list def A_to_Jtensor(self): for key, val in self.JJ.items(): # key:(R, iatom, jatom) R, iatom, jatom = key ispin = self.ispin(iatom) jspin = self.ispin(jatom) keyspin = (R, ispin, jspin) is_nonself = not (R == (0, 0, 0) and iatom == jatom) Jij = np.imag(val) / np.sign( np.dot(self.spinat[iatom], self.spinat[jatom])) Jorbij = np.imag(self.Jorb[key]) / np.sign( np.dot(self.spinat[iatom], self.spinat[jatom])) if is_nonself: self.exchange_Jdict[keyspin] = Jij self.exchange_Jdict_orb[keyspin] = Jorbij def get_rho_e(self, rho_up, rho_dn): #self.rho_up_list.append(-1.0 / np.pi * np.imag(rho_up[(0,0,0)])) #self.rho_dn_list.append(-1.0 / np.pi * np.imag(rho_dn[(0,0,0)])) rup = -1.0 / np.pi * rho_up[(0, 0, 0)] rdn = -1.0 / np.pi * rho_dn[(0, 0, 0)] return rup, rdn def get_rho_atom(self): """ charges and spins from density matrices """ self.charges = np.zeros(len(self.atoms), dtype=float) self.spinat = np.zeros((len(self.atoms), 3), dtype=float) for iatom in self.orb_dict: iorb = self.iorb(iatom) tup = np.real(np.trace(self.rho_up[np.ix_(iorb, iorb)])) tdn = np.real(np.trace(self.rho_dn[np.ix_(iorb, iorb)])) self.charges[iatom] = tup + tdn self.spinat[iatom, 2] = tup - tdn def finalize(self): self.Gup.clean_cache() self.Gdn.clean_cache() path = 'TB2J_results/cache' if os.path.exists(path): shutil.rmtree(path) def integrate(self, method="simpson"): if method == "trapezoidal": integrate = trapezoidal_nonuniform elif method == 'simpson': integrate = simpson_nonuniform self.rho_up = np.imag(integrate(self.contour.path, self.rho_up_list)) self.rho_dn = np.imag(integrate(self.contour.path, self.rho_dn_list)) for R, ijpairs in self.R_ijatom_dict.items(): for iatom, jatom in ijpairs: self.Jorb[(R, iatom, jatom)] = integrate( self.contour.path, self.Jorb_list[(R, iatom, jatom)]) self.JJ[(R, iatom, jatom)] = integrate(self.contour.path, self.JJ_list[(R, iatom, jatom)]) def get_AijR_rhoR(self, e): GR_up, rho_up = self.Gup.get_GR(self.short_Rlist, energy=e, get_rho=True) GR_dn, rho_dn = self.Gdn.get_GR(self.short_Rlist, energy=e, get_rho=True) rup, rdn = self.get_rho_e(rho_up, rho_dn) Jorb_list, JJ_list = self.get_all_A(GR_up, GR_dn) return rup, rdn, Jorb_list, JJ_list def calculate_all(self): """ The top level. """ print("Green's function Calculation started.") widgets = [ ' [', progressbar.Timer(), '] ', progressbar.Bar(), ' (', progressbar.ETA(), ') ', ] bar = progressbar.ProgressBar(maxval=len(self.contour.path), widgets=widgets) bar.start() if self.np == 1: results = map(self.get_AijR_rhoR, self.contour.path) else: pool = ProcessPool(nodes=self.np) results = pool.map(self.get_AijR_rhoR, self.contour.path) for i, result in enumerate(results): bar.update(i) rup, rdn, Jorb_list, JJ_list = result self.rho_up_list.append(rup) self.rho_dn_list.append(rdn) for iR, R in enumerate(self.R_ijatom_dict): for (iatom, jatom) in self.R_ijatom_dict[R]: key = (R, iatom, jatom) self.Jorb_list[key].append(Jorb_list[key]) self.JJ_list[key].append(JJ_list[key]) if self.np > 1: pool.close() pool.join() pool.clear() self.integrate() self.get_rho_atom() self.A_to_Jtensor() bar.finish() def write_output(self, path='TB2J_results'): self._prepare_index_spin() output = SpinIO( atoms=self.atoms, charges=self.charges, spinat=self.spinat, index_spin=self.index_spin, colinear=True, distance_dict=self.distance_dict, exchange_Jdict=self.exchange_Jdict, exchange_Jdict_orb=self.exchange_Jdict_orb, dmi_ddict=None, NJT_Jdict=None, NJT_ddict=None, Jani_dict=None, biquadratic_Jdict=None, description=self.description, ) output.write_all(path=path)
class ExchangeNCL(Exchange): """ Non-collinear exchange """ def set_tbmodels(self, tbmodels): """ tbmodels should be in spinor form. The basis should be orb1_up, orb2_up,...orbn_up, orb1_dn, orb2_dn.... """ self.tbmodel = tbmodels # TODO: check if tbmodels are really a tbmodel with SOC. self.G = TBGreen(self.tbmodel, self.kmesh, self.efermi, use_cache=self._use_cache) self.norb = self.G.norb self.nbasis = self.G.nbasis self.rho = np.zeros((self.nbasis, self.nbasis), dtype=complex) self.A_ijR = defaultdict(lambda: np.zeros((4, 4), dtype=complex)) #self.HR0 = self.tbmodel.ham_R0 self.HR0 = self.G.H0 self._is_collinear = False self.Pdict = {} def _prepare_NijR(self): self.N = {} for R in self.Rlist: self.N[R] = np.zeros((self.nbasis, self.nbasis), dtype=complex) def _prepare_Patom(self): for iatom in self.ind_mag_atoms: if self.tbmodel.is_siesta: self.Pdict[iatom] = pauli_block_sigma_norm_wrongy( self.get_H_atom(iatom)) else: self.Pdict[iatom] = pauli_block_sigma_norm( self.get_H_atom(iatom)) def get_H_atom(self, iatom): orbs = self.iorb(iatom) return self.HR0[np.ix_(orbs, orbs)] def get_P_iatom(self, iatom): """ Calculate the norm of the Hamiltonian vector. For each atom, the local hamiltonian of each orbital H(2*2 matrix) can be written as H0* I + H1* sigma1 + H2*sigma2 + H3 *sigma3 where sigma is a Pauli matrix. return the norm of (H1, H2, H3) vector :param iatom: index of atom :returns: a matrix of norms P. P[i, j] is for orbital i and j. :rtype: complex matrix of shape norb_i * norb_i. """ if self.Pdict == {}: self._prepare_Patom() return self.Pdict[iatom] def GR_atom(self, GR, iatom, jatom): """Given a green's function matrix, return the [iatom, jatom] component. :param GR: Green's function matrix :param iatom: index of atom i :param jatom: index of atom j :returns: G_ij :rtype: complex matrix. """ orbi = self.iorb(iatom) orbj = self.iorb(jatom) return GR[np.ix_(orbi, orbj)] def get_A_ijR(self, G, iatom, jatom, de): """ calculate A from G for a energy slice (de). It take the .. math:: A^{uv} = p T^u p T^v dE / pi where u, v are I, x, y, z (index 0, 1,2,3). p(i) = self.get_P_iatom(iatom) T^u(ijR) (u=0,1,2,3) = pauli_block_all(G) :param G: Green's function for all R, i, j. :param iatom: i :param jatom: j :param de: energy step. used for integeration :returns: a matrix of A_ij(u, v), where u, v =(0)0, x(1), y(2), z(3) :rtype: 4*4 matrix """ for R in self.R_ijatom_dict: # G[i, j, R] GR = G[R] if (iatom, jatom) in self.R_ijatom_dict[R]: Gij = self.GR_atom(GR, iatom, jatom) # GijR , I, x, y, z component. if self.tbmodel.is_siesta: Gij_Ixyz = pauli_block_all_wrongy(Gij) else: Gij_Ixyz = pauli_block_all(Gij) # G(j, i, -R) Rm = tuple(-x for x in R) GRm = G[Rm] Gji = self.GR_atom(GRm, jatom, iatom) if self.tbmodel.is_siesta: Gji_Ixyz = pauli_block_all_wrongy(Gji) else: Gji_Ixyz = pauli_block_all(Gji) tmp = np.zeros((4, 4), dtype=complex) for a in range(4): for b in range(4): AijRab = np.matmul( np.matmul(self.get_P_iatom(iatom), Gij_Ixyz[a]), np.matmul(self.get_P_iatom(jatom), Gji_Ixyz[b])) # trace over orb tmp[a, b] = np.trace(AijRab) # Note: the full complex, rather than Re or Im part is stored into A_ijR. self.A_ijR[(R, iatom, jatom)] += tmp * de / np.pi def get_all_A(self, G, de): """ Calculate all A matrix elements Loop over all magnetic atoms. :param G: Green's function. :param de: energy step. """ for iatom in self.ind_mag_atoms: for jatom in self.ind_mag_atoms: self.get_A_ijR(G, iatom, jatom, de) def A_to_Jtensor(self): """ Calculate J tensors from A. If we assume the exchange can be written as a bilinear tensor form, J_{isotropic} = Tr Im (A^{00} - A^{xx} - A^{yy} - A^{zz}) J_{anisotropic}_uv = Tr Im (2A) DMI = Tr Re (A^{0z} - A^{z0} ) """ self.Jani = {} self.DMI = {} self.Jprime = {} self.B = {} self.exchange_Jdict = {} self.debug_dict = {'DMI2': {}} for key, val in self.A_ijR.items(): # key:(R, iatom, jatom) R, iatom, jatom = key Rm = tuple(-x for x in R) valm = self.A_ijR[(Rm, jatom, iatom)] ispin = self.ispin(iatom) jspin = self.ispin(jatom) keyspin = (R, ispin, jspin) is_nonself = not (R == (0, 0, 0) and iatom == jatom) Jiso = np.zeros((3, 3), dtype=float) Ja = np.zeros((3, 3), dtype=float) Dtmp = np.zeros(3, dtype=float) Dtmp2 = np.zeros(3, dtype=float) # Heisenberg like J. for i in range(3): Jiso[i, i] += np.imag(val[0, 0] - val[1, 1] - val[2, 2] - val[3, 3]) #Jiso[i, i] += np.imag(val[0, 0] - val[3, 3]) if is_nonself: self.exchange_Jdict[keyspin] = Jiso[0, 0] # off-diagonal ansiotropic exchange for i in range(3): for j in range(3): #Ja[i,j] = np.imag(val[i + 1, j + 1] + valm[i + 1, j + 1]) Ja[i, j] = np.imag(val[i + 1, j + 1] + valm[i + 1, j + 1]) #Ja[i,j] = -np.imag(val[i+1, j+1]) if is_nonself: self.Jani[keyspin] = Ja # DMI for i in range(3): Dtmp[i] = np.real(val[0, i + 1] - val[i + 1, 0]) # Dx = Jyz-Jzy # Dy = Jzx-Jxz # Dz = Jxy-Jyx Dtmp2[0] = np.imag(val[2, 3] - val[3, 2]) Dtmp2[1] = np.imag(val[3, 1] - val[1, 3]) Dtmp2[2] = np.imag(val[1, 2] - val[2, 1]) if is_nonself: self.DMI[keyspin] = Dtmp self.debug_dict['DMI2'][keyspin] = Dtmp2 # isotropic exchange into bilinear and biqudratic parts: # Jprime SiSj and B (SiSj)^2 if is_nonself: Si = self.spinat[iatom] Sj = self.spinat[jatom] Jprime = np.imag(val[0, 0] - val[3, 3]) - 2 * np.sign( np.dot(Si, Sj)) * np.imag(val[3, 3]) #Jprime = np.imag(val[0, 0] - 3*val[3, 3]) B = np.imag(val[3, 3]) self.B[keyspin] = Jprime, B def get_N_e(self, GR, de): """ calcualte density matrix for all R,i, j """ self.N = defaultdict(lambda: 0.0) for R, G in GR.items(): self.N[R] += -1.0 / np.pi * np.imag(G * de) def get_rho_e(self, rhoR, de): """ add component to density matrix from a green's function :param GR: Green's funciton in real space. :param de: energy step """ self.rho += -1.0 / np.pi * rhoR[0, 0, 0] * de def get_total_charges(self): return np.sum(np.imag(np.diag(self.rho))) def get_rho_atom(self): """ calculate charge and spin for each atom. """ rho = {} self.charges = np.zeros(len(self.atoms), dtype=float) self.spinat = np.zeros((len(self.atoms), 3), dtype=float) for iatom in self.orb_dict: iorb = self.iorb(iatom) tmp = self.rho[np.ix_(iorb, iorb)] # *2 because there is a 1/2 in the paui_block_all function if self.tbmodel.is_siesta: rho[iatom] = np.array( [np.trace(x) * 2 for x in pauli_block_all_wrongy(tmp)]) else: rho[iatom] = np.array( [np.trace(x) * 2 for x in pauli_block_all(tmp)]) self.charges[iatom] = np.imag(rho[iatom][0]) self.spinat[iatom, :] = np.imag(rho[iatom][1:]) self.rho_dict = rho return self.rho_dict def calculate_DMI_NJT(self): """ calculate exchange and DMI with the D(i,j) = """ Ddict_NJT = {} Jdict_NJT = {} for R in self.short_Rlist: N = self.N[tuple(-np.array(R))] # density matrix t = self.tbmodel.get_hamR(R) # hopping parameter for iatom in self.ind_mag_atoms: orbi = self.iorb(iatom) ni = len(orbi) for jatom in self.ind_mag_atoms: orbj = self.iorb(jatom) nj = len(orbj) Nji = N[np.ix_(orbj, orbi)] tij = t[np.ix_(orbi, orbj)] D = np.zeros(3, dtype=float) J = np.zeros(3, dtype=float) for dim in range(3): #S_i = pauli_mat(ni, dim + # 1) #*self.rho[np.ix_(orbi, orbi)] #S_j = pauli_mat(nj, dim + # 1) #*self.rho[np.ix_(orbj, orbj)] # TODO: Note that rho is complex, not the imaginary part S_i = pauli_mat(ni, dim + 1) * self.rho[np.ix_( orbi, orbi)] S_j = pauli_mat(nj, dim + 1) * self.rho[np.ix_( orbj, orbj)] # [S, t]+ = Si tij + tij Sj, where # Si and Sj are the spin operator # Here we do not have L operator, so J-> S Jt = np.matmul(S_i, tij) + np.matmul(tij, S_j) Jtminus = np.matmul(S_i, tij) - np.matmul(tij, S_j) # D = -1/2 Tr Nji [J, tij] # Trace over spin and orb D[dim] = -0.5 * np.imag(np.trace(np.matmul(Nji, Jt))) J[dim] = -0.5 * np.imag( np.trace(np.matmul(Nji, Jtminus))) ispin = self.ispin(iatom) jspin = self.ispin(jatom) Ddict_NJT[(R, ispin, jspin)] = D Jdict_NJT[(R, ispin, jspin)] = J self.Jdict_NJT = Jdict_NJT self.Ddict_NJT = Ddict_NJT return Ddict_NJT def calculate_all(self): """ The top level. """ print("Green's function Calculation started.") widgets = [ ' [', progressbar.Timer(), '] ', progressbar.Bar(), ' (', progressbar.ETA(), ') ', ] bar = progressbar.ProgressBar(maxval=self.contour.npoints, widgets=widgets) bar.start() for ie in range(self.contour.npoints): bar.update(ie) #if self.ne is not none and self.get_total_charges( #) > self.ne and ie not in self.elistc: # self._prepare_elistc(self, ie) # continue e = self.contour.path[ie] de = self.contour.de[ie] GR, rhoR = self.G.get_GR(self.short_Rlist, energy=e, get_rho=True) self.get_rho_e(rhoR, de) self.get_all_A(GR, de) if self.calc_NJt: self.get_N_e(GR, de) self.get_rho_atom() self.A_to_Jtensor() #self.calculate_DMI_NJT() bar.finish() def _prepare_index_spin(self): # index_spin: index in spin hamiltonian of atom. starts from 1. -1 means not considered. ind_matoms = [] self.index_spin = [] ispin = 0 for i, sym in enumerate(self.atoms.get_chemical_symbols()): if sym in self.magnetic_elements: ind_matoms.append(i) self.index_spin.append(ispin) ispin += 1 else: self.index_spin.append(-1) def write_output(self, path='TB2J_results'): self._prepare_index_spin() output = SpinIO( atoms=self.atoms, charges=self.charges, spinat=self.spinat, index_spin=self.index_spin, colinear=False, distance_dict=self.distance_dict, exchange_Jdict=self.exchange_Jdict, dmi_ddict=self.DMI, NJT_Jdict=self.Jdict_NJT, NJT_ddict=self.Ddict_NJT, Jani_dict=self.Jani, biquadratic_Jdict=self.B, debug_dict=self.debug_dict, description=self.description, ) output.write_all(path=path) def finalize(self): self.G.clean_cache() def run(self, path='TB2J_results'): self.calculate_all() self.write_output(path=path) self.finalize()
class ExchangeNCL(Exchange): """ Non-collinear exchange """ def set_tbmodels(self, tbmodels): """ tbmodels should be in spinor form. The basis should be orb1_up, orb2_up,...orbn_up, orb1_dn, orb2_dn.... """ self.tbmodel = tbmodels self.backend_name = self.tbmodel.name # TODO: check if tbmodels are really a tbmodel with SOC. self.G = TBGreen(self.tbmodel, self.kmesh, self.efermi, use_cache=self._use_cache, nproc=self.np) self.norb = self.G.norb self.nbasis = self.G.nbasis self.rho = np.zeros((self.nbasis, self.nbasis), dtype=complex) self.A_ijR_list = defaultdict(lambda: []) self.A_ijR = defaultdict(lambda: np.zeros((4, 4), dtype=complex)) self.A_ijR_orb = dict() self.HR0 = self.G.H0 self._is_collinear = False self.Pdict = {} def _prepare_NijR(self): self.N = {} for R in self.Rlist: self.N[R] = np.zeros((self.nbasis, self.nbasis), dtype=complex) def _prepare_Patom(self): for iatom in self.ind_mag_atoms: self.Pdict[iatom] = pauli_block_sigma_norm(self.get_H_atom(iatom)) def get_H_atom(self, iatom): orbs = self.iorb(iatom) # return self.HR0[self.orb_slice[iatom], self.orb_slice[iatom]] return self.HR0[np.ix_(orbs, orbs)] def get_P_iatom(self, iatom): """ Calculate the norm of the Hamiltonian vector. For each atom, the local hamiltonian of each orbital H(2*2 matrix) can be written as H0* I + H1* sigma1 + H2*sigma2 + H3 *sigma3 where sigma is a Pauli matrix. return the norm of (H1, H2, H3) vector :param iatom: index of atom :returns: a matrix of norms P. P[i, j] is for orbital i and j. :rtype: complex matrix of shape norb_i * norb_i. """ if self.Pdict == {}: self._prepare_Patom() return self.Pdict[iatom] def GR_atom(self, GR, iatom, jatom): """Given a green's function matrix, return the [iatom, jatom] component. :param GR: Green's function matrix :param iatom: index of atom i :param jatom: index of atom j :returns: G_ij :rtype: complex matrix. """ orbi = self.iorb(iatom) orbj = self.iorb(jatom) return GR[np.ix_(orbi, orbj)] # return GR[self.orb_slice[iatom], self.orb_slice[jatom]] def get_A_ijR(self, G, R, iatom, jatom): """ calculate A from G for a energy slice (de). It take the .. math:: A^{uv} = p T^u p T^v dE / pi where u, v are I, x, y, z (index 0, 1,2,3). p(i) = self.get_P_iatom(iatom) T^u(ijR) (u=0,1,2,3) = pauli_block_all(G) :param G: Green's function for all R, i, j. :param iatom: i :param jatom: j :param de: energy step. used for integeration :returns: a matrix of A_ij(u, v), where u, v =(0)0, x(1), y(2), z(3) :rtype: 4*4 matrix """ GR = G[R] Gij = self.GR_atom(GR, iatom, jatom) Gij_Ixyz = pauli_block_all(Gij) # G(j, i, -R) Rm = tuple(-x for x in R) GRm = G[Rm] Gji = self.GR_atom(GRm, jatom, iatom) Gji_Ixyz = pauli_block_all(Gji) ni = self.norb_reduced[iatom] nj = self.norb_reduced[jatom] tmp = np.zeros((4, 4), dtype=complex) if self.orb_decomposition: torb = np.zeros((4, 4, ni, nj), dtype=complex) # a, b in (0,x,y,z) for a in range(4): piGij = self.get_P_iatom(iatom) @ Gij_Ixyz[a] for b in range(4): pjGji = self.get_P_iatom(jatom) @ Gji_Ixyz[b] torb[a, b] = self.simplify_orbital_contributions( np.einsum('ij, ji -> ij', piGij, pjGji) / np.pi, iatom, jatom) tmp[a, b] = np.sum(torb[a, b]) else: for a in range(4): pGp = self.get_P_iatom(iatom) @ Gij_Ixyz[a] @ self.get_P_iatom( jatom) for b in range(4): AijRab = pGp @ Gji_Ixyz[b] tmp[a, b] = np.trace(AijRab) / np.pi torb = None return tmp, torb def get_all_A(self, G): """ Calculate all A matrix elements Loop over all magnetic atoms. :param G: Green's function. :param de: energy step. """ A_ijR_list = {} Aorb_ijR_list = {} for iR, R in enumerate(self.R_ijatom_dict): for (iatom, jatom) in self.R_ijatom_dict[R]: A, A_orb = self.get_A_ijR(G, R, iatom, jatom) A_ijR_list[(R, iatom, jatom)] = A Aorb_ijR_list[(R, iatom, jatom)] = A_orb return A_ijR_list, Aorb_ijR_list def A_to_Jtensor_orb(self): """ convert the orbital composition of A into J, DMI, Jani """ self.Jiso_orb = {} self.Jani_orb = {} self.DMI_orb = {} if self.orb_decomposition: for key, val in self.A_ijR_orb.items(): R, iatom, jatom = key Rm = tuple(-x for x in R) valm = self.A_ijR_orb[(Rm, jatom, iatom)] ni = self.norb_reduced[iatom] nj = self.norb_reduced[jatom] is_nonself = not (R == (0, 0, 0) and iatom == jatom) ispin = self.ispin(iatom) jspin = self.ispin(jatom) keyspin = (R, ispin, jspin) # isotropic J Jiso = np.imag(val[0, 0] - val[1, 1] - val[2, 2] - val[3, 3]) # off-diagonal anisotropic exchange Ja = np.zeros((3, 3, ni, nj), dtype=float) for i in range(3): for j in range(3): Ja[i, j] = np.imag(val[i + 1, j + 1] + valm[i + 1, j + 1]) # DMI Dtmp = np.zeros((3, ni, nj), dtype=float) for i in range(3): Dtmp[i] = np.real(val[0, i + 1] - val[i + 1, 0]) if is_nonself: self.Jiso_orb[keyspin] = Jiso self.Jani_orb[keyspin] = Ja self.DMI_orb[keyspin] = Dtmp def A_to_Jtensor(self): """ Calculate J tensors from A. If we assume the exchange can be written as a bilinear tensor form, J_{isotropic} = Tr Im (A^{00} - A^{xx} - A^{yy} - A^{zz}) J_{anisotropic}_uv = Tr Im (2A) DMI = Tr Re (A^{0z} - A^{z0} ) """ self.Jani = {} self.DMI = {} self.Jprime = {} self.B = {} self.exchange_Jdict = {} self.Jiso_orb = {} self.debug_dict = {'DMI2': {}} for key, val in self.A_ijR.items(): # key:(R, iatom, jatom) R, iatom, jatom = key Rm = tuple(-x for x in R) valm = self.A_ijR[(Rm, jatom, iatom)] ispin = self.ispin(iatom) jspin = self.ispin(jatom) keyspin = (R, ispin, jspin) is_nonself = not (R == (0, 0, 0) and iatom == jatom) Jiso = 0.0 Ja = np.zeros((3, 3), dtype=float) Dtmp = np.zeros(3, dtype=float) Dtmp2 = np.zeros(3, dtype=float) # Heisenberg like J. Jiso = np.imag(val[0, 0] - val[1, 1] - val[2, 2] - val[3, 3]) if is_nonself: self.exchange_Jdict[keyspin] = Jiso # off-diagonal anisotropic exchange for i in range(3): for j in range(3): Ja[i, j] = np.imag(val[i + 1, j + 1] + valm[i + 1, j + 1]) if is_nonself: self.Jani[keyspin] = Ja # DMI for i in range(3): Dtmp[i] = np.real(val[0, i + 1] - val[i + 1, 0]) # Dx = Jyz-Jzy # Dy = Jzx-Jxz # Dz = Jxy-Jyx Dtmp2[0] = np.imag(val[2, 3] - val[3, 2]) Dtmp2[1] = np.imag(val[3, 1] - val[1, 3]) Dtmp2[2] = np.imag(val[1, 2] - val[2, 1]) if is_nonself: self.DMI[keyspin] = Dtmp self.debug_dict['DMI2'][keyspin] = Dtmp2 # isotropic exchange into bilinear and biqudratic parts: # Jprime SiSj and B (SiSj)^2 if is_nonself: Si = self.spinat[iatom] Sj = self.spinat[jatom] Jprime = np.imag(val[0, 0] - val[3, 3]) - 2 * np.sign( np.dot(Si, Sj)) * np.imag(val[3, 3]) # Jprime = np.imag(val[0, 0] - 3*val[3, 3]) B = np.imag(val[3, 3]) self.B[keyspin] = Jprime, B def get_N_e(self, GR, de): """ calcualte density matrix for all R,i, j """ self.N = defaultdict(lambda: 0.0) for R, G in GR.items(): self.N[R] += -1.0 / np.pi * np.imag(G * de) def get_rho_e(self, rhoR): """ add component to density matrix from a green's function :param GR: Green's funciton in real space. """ return -1.0 / np.pi * rhoR[0, 0, 0] def get_total_charges(self): return np.sum(np.imag(np.diag(self.rho))) def get_rho_atom(self): """ calculate charge and spin for each atom. """ rho = {} self.charges = np.zeros(len(self.atoms), dtype=float) self.spinat = np.zeros((len(self.atoms), 3), dtype=float) for iatom in self.orb_dict: iorb = self.iorb(iatom) tmp = self.rho[np.ix_(iorb, iorb)] # *2 because there is a 1/2 in the paui_block_all function rho[iatom] = np.array( [np.trace(x) * 2 for x in pauli_block_all(tmp)]) self.charges[iatom] = np.imag(rho[iatom][0]) self.spinat[iatom, :] = np.imag(rho[iatom][1:]) self.rho_dict = rho return self.rho_dict def calculate_DMI_NJT(self): """ calculate exchange and DMI with the D(i,j) = """ Ddict_NJT = {} Jdict_NJT = {} for R in self.short_Rlist: N = self.N[tuple(-np.array(R))] # density matrix t = self.tbmodel.get_hamR(R) # hopping parameter for iatom in self.ind_mag_atoms: orbi = self.iorb(iatom) ni = len(orbi) for jatom in self.ind_mag_atoms: orbj = self.iorb(jatom) nj = len(orbj) Nji = N[np.ix_(orbj, orbi)] tij = t[np.ix_(orbi, orbj)] D = np.zeros(3, dtype=float) J = np.zeros(3, dtype=float) for dim in range(3): # S_i = pauli_mat(ni, dim + # 1) #*self.rho[np.ix_(orbi, orbi)] # S_j = pauli_mat(nj, dim + # 1) #*self.rho[np.ix_(orbj, orbj)] # TODO: Note that rho is complex, not the imaginary part S_i = pauli_mat(ni, dim + 1) * self.rho[np.ix_( orbi, orbi)] S_j = pauli_mat(nj, dim + 1) * self.rho[np.ix_( orbj, orbj)] # [S, t]+ = Si tij + tij Sj, where # Si and Sj are the spin operator # Here we do not have L operator, so J-> S Jt = np.matmul(S_i, tij) + np.matmul(tij, S_j) Jtminus = np.matmul(S_i, tij) - np.matmul(tij, S_j) # D = -1/2 Tr Nji [J, tij] # Trace over spin and orb D[dim] = -0.5 * np.imag(np.trace(np.matmul(Nji, Jt))) J[dim] = -0.5 * np.imag( np.trace(np.matmul(Nji, Jtminus))) ispin = self.ispin(iatom) jspin = self.ispin(jatom) Ddict_NJT[(R, ispin, jspin)] = D Jdict_NJT[(R, ispin, jspin)] = J self.Jdict_NJT = Jdict_NJT self.Ddict_NJT = Ddict_NJT return Ddict_NJT def integrate(self, rhoRs, AijRs, AijRs_orb=None, method='simpson'): """ AijRs: a list of AijR, wherer AijR: array of ((nR, n, n, 4,4), dtype=complex) """ if method == "trapezoidal": integrate = trapezoidal_nonuniform elif method == 'simpson': integrate = simpson_nonuniform self.rho = integrate(self.contour.path, rhoRs) for iR, R in enumerate(self.R_ijatom_dict): for (iatom, jatom) in self.R_ijatom_dict[R]: f = AijRs[(R, iatom, jatom)] self.A_ijR[(R, iatom, jatom)] = integrate(self.contour.path, f) if self.orb_decomposition: self.A_ijR_orb[(R, iatom, jatom)] = integrate( self.contour.path, AijRs_orb[(R, iatom, jatom)]) def get_AijR_rhoR(self, e): GR, rhoR = self.G.get_GR(self.short_Rlist, energy=e, get_rho=True) AijR, AijR_orb = self.get_all_A(GR) return AijR, AijR_orb, self.get_rho_e(rhoR) def save_AijR(self, AijRs, fname): result = dict(path=self.contour.path, AijRs=AijRs) with open(fname, 'wb') as myfile: pickle.dump(result, myfile) def calculate_all(self): """ The top level. """ print("Green's function Calculation started.") rhoRs = [] GRs = [] AijRs = {} AijRs_orb = {} npole = len(self.contour.path) if self.np > 1: #executor = ProcessPool(nodes=self.np) #results = executor.map(self.get_AijR_rhoR, self.contour.path) results = p_map(self.get_AijR_rhoR, self.contour.path, num_cpus=self.np) else: results = map(self.get_AijR_rhoR, tqdm(self.contour.path, total=npole)) for i, result in enumerate(results): for iR, R in enumerate(self.R_ijatom_dict): for (iatom, jatom) in self.R_ijatom_dict[R]: if (R, iatom, jatom) in AijRs: AijRs[(R, iatom, jatom)].append(result[0][R, iatom, jatom]) if self.orb_decomposition: AijRs_orb[(R, iatom, jatom)].append(result[1][R, iatom, jatom]) else: AijRs[(R, iatom, jatom)] = [] AijRs[(R, iatom, jatom)].append(result[0][R, iatom, jatom]) if self.orb_decomposition: AijRs_orb[(R, iatom, jatom)] = [] AijRs_orb[(R, iatom, jatom)].append(result[1][R, iatom, jatom]) rhoRs.append(result[2]) if self.np > 1: # executor.close() # executor.join() # executor.clear() pass # self.save_AijRs(AijRs) self.integrate(rhoRs, AijRs, AijRs_orb) self.get_rho_atom() self.A_to_Jtensor() self.A_to_Jtensor_orb() def _prepare_index_spin(self): # index_spin: index in spin hamiltonian of atom. starts from 1. -1 means not considered. ind_matoms = [] self.index_spin = [] ispin = 0 for i, sym in enumerate(self.atoms.get_chemical_symbols()): if sym in self.magnetic_elements: ind_matoms.append(i) self.index_spin.append(ispin) ispin += 1 else: self.index_spin.append(-1) def write_output(self, path='TB2J_results'): self._prepare_index_spin() output = SpinIO( atoms=self.atoms, charges=self.charges, spinat=self.spinat, index_spin=self.index_spin, colinear=False, orbital_names=self.orbital_names, distance_dict=self.distance_dict, exchange_Jdict=self.exchange_Jdict, Jiso_orb=self.Jiso_orb, dmi_ddict=self.DMI, Jani_dict=self.Jani, DMI_orb=self.DMI_orb, Jani_orb=self.Jani_orb, NJT_Jdict=self.Jdict_NJT, NJT_ddict=self.Ddict_NJT, biquadratic_Jdict=self.B, debug_dict=self.debug_dict, description=self.description, ) output.write_all(path=path) # with open("TB2J_results/J_orb.pickle", 'wb') as myfile: # pickle.dump({'Jiso_orb': self.Jiso_orb, # 'DMI_orb': self.DMI_orb, 'Jani_orb': self.Jani_orb}, myfile) def finalize(self): self.G.clean_cache() def run(self, path='TB2J_results'): self.calculate_all() self.write_output(path=path) self.finalize()