def getDipoleOperator(nBaths, n): r""" Return dipole transition operator :math:`\hat{T}`. Transition between states of different angular momentum, defined by the keys in the nBaths dictionary. Parameters ---------- nBaths : Ordered dict int : int, where the keys are angular momenta and values are number of bath states. n : list polarization vector n = [nx,ny,nz] """ tOp = {} nDict = { -1: (n[0] + 1j * n[1]) / sqrt(2), 0: n[2], 1: (-n[0] + 1j * n[1]) / sqrt(2), } # Angular momentum l1, l2 = nBaths.keys() for m in range(-l2, l2 + 1): for mp in range(-l1, l1 + 1): for s in range(2): if abs(m - mp) <= 1: # See Robert Eder's lecture notes: # "Multiplets in Transition Metal Ions" # in Julich school. # tij = d*n*c1(l=2,m;l=1,mp), # d - radial integral # n - polarization vector # c - Gaunt coefficient tij = gauntC(k=1, l=l2, m=m, lp=l1, mp=mp, prec=16) tij *= nDict[m - mp] if tij != 0: i = c2i(nBaths, (l2, s, m)) j = c2i(nBaths, (l1, s, mp)) tOp[((i, "c"), (j, "a"))] = tij return tOp
def getPhotoEmissionOperators(nBaths, l=2): r""" Return photo emission operators :math:`\{ c_i \}`. Parameters ---------- nBaths : OrderedDict Angular momentum: number of bath states. l : int Angular momentum. """ # Transition operators tOpsPS = [] for s in range(2): for m in range(-l, l + 1): tOpsPS.append({((c2i(nBaths, (l, s, m)), "a"), ): 1}) return tOpsPS
def get_hamiltonian_operator_using_CF( nBaths, nValBaths, slaterCondon, SOCs, DCinfo, hField, h0_CF_filename, bath_state_basis="spherical", ): """ Return the Hamiltonian, in operator form. Parameters ---------- nBaths : dict Number of bath states for each angular momentum. nValBaths : dict Number of valence bath states for each angular momentum. slaterCondon : list List of Slater-Condon parameters. SOCs : list List of SOC parameters. DCinfo : list Contains information needed for the double counting energy. hField : list External magnetic field. Elements hx,hy,hz h0_CF_filename : str Filename of the non-relativistic non-interacting CF Hamiltonian operator, in json-format. bath_state_basis : str 'spherical' or 'cubic'. Which basis to use for the bath states. Returns ------- hOp : dict The Hamiltonian in operator form. tuple : complex, where each tuple describes a process of several steps. Each step is described by a tuple of the form: (i,'c') or (i,'a'), where i is a spin-orbital index. """ # Divide up input parameters to more concrete variables Fdd, Fpp, Fpd, Gpd = slaterCondon xi_2p, xi_3d = SOCs n0imps, chargeTransferCorrection = DCinfo hx, hy, hz = hField # Calculate the U operator, in spherical harmonics basis. uOperator = finite.get2p3dSlaterCondonUop(Fdd=Fdd, Fpp=Fpp, Fpd=Fpd, Gpd=Gpd) # Add SOC, in spherical harmonics basis. SOC2pOperator = finite.getSOCop(xi_2p, l=1) SOC3dOperator = finite.getSOCop(xi_3d, l=2) # Double counting (DC) correction values. # MLFT DC dc = finite.dc_MLFT( n3d_i=n0imps[2], c=chargeTransferCorrection, Fdd=Fdd, n2p_i=n0imps[1], Fpd=Fpd, Gpd=Gpd, ) eDCOperator = {} for il, l in enumerate([2, 1]): for s in range(2): for m in range(-l, l + 1): eDCOperator[(((l, s, m), "c"), ((l, s, m), "a"))] = -dc[il] # Magnetic field hHfieldOperator = finite.gethHfieldop(hx, hy, hz, l=2) # Construct non-relativistic and non-interacting Hamiltonian, from CF parameters. h0_operator = get_CF_hamiltonian(nBaths, nValBaths, h0_CF_filename, bath_state_basis) # Add Hamiltonian terms to one operator. hOperator = finite.addOps( [ uOperator, hHfieldOperator, SOC2pOperator, SOC3dOperator, eDCOperator, h0_operator, ] ) # Convert spin-orbital and bath state indices to a single index notation. hOp = {} for process, value in hOperator.items(): hOp[tuple((c2i(nBaths, spinOrb), action) for spinOrb, action in process)] = value return hOp
def main( h0_CF_filename, radial_filename, ls, nBaths, nValBaths, n0imps, dnTols, dnValBaths, dnConBaths, Fdd, Fpp, Fpd, Gpd, xi_2p, xi_3d, chargeTransferCorrection, hField, nPsiMax, nPrintSlaterWeights, tolPrintOccupation, T, energy_cut, delta, deltaRIXS, deltaNIXS, ): """ First find the lowest eigenstates and then use them to calculate various spectra. Parameters ---------- h0_CF_filename : str Filename of the non-relativistic non-interacting CF Hamiltonian operator, in json-format. radial_filename : str File name of file containing radial mesh and radial part of final and initial orbitals in the NIXS excitation process. ls : tuple Angular momenta of correlated orbitals. nBaths : tuple Number of bath states, for each angular momentum. nValBaths : tuple Number of valence bath states, for each angular momentum. n0imps : tuple Initial impurity occupation. dnTols : tuple Max devation from initial impurity occupation, for each angular momentum. dnValBaths : tuple Max number of electrons to leave valence bath orbitals, for each angular momentum. dnConBaths : tuple Max number of electrons to enter conduction bath orbitals, for each angular momentum. Fdd : tuple Slater-Condon parameters Fdd. This assumes d-orbitals. Fpp : tuple Slater-Condon parameters Fpp. This assumes p-orbitals. Fpd : tuple Slater-Condon parameters Fpd. This assumes p- and d-orbitals. Gpd : tuple Slater-Condon parameters Gpd. This assumes p- and d-orbitals. xi_2p : float SOC value for p-orbitals. This assumes p-orbitals. xi_3d : float SOC value for d-orbitals. This assumes d-orbitals. chargeTransferCorrection : float Double counting parameter hField : tuple Magnetic field. nPsiMax : int Maximum number of eigenstates to consider. nPrintSlaterWeights : int Printing parameter. tolPrintOccupation : float Printing parameter. T : float Temperature (Kelvin) energy_cut : float How many k_B*T above lowest eigenenergy to consider. delta : float Smearing, half width half maximum (HWHM). Due to short core-hole lifetime. deltaRIXS : float Smearing, half width half maximum (HWHM). Due to finite lifetime of excited states. deltaNIXS : float Smearing, half width half maximum (HWHM). Due to finite lifetime of excited states. """ # MPI variables comm = MPI.COMM_WORLD rank = comm.rank if rank == 0: t0 = time.time() # -- System information -- nBaths = OrderedDict(zip(ls, nBaths)) nValBaths = OrderedDict(zip(ls, nValBaths)) # -- Basis occupation information -- n0imps = OrderedDict(zip(ls, n0imps)) dnTols = OrderedDict(zip(ls, dnTols)) dnValBaths = OrderedDict(zip(ls, dnValBaths)) dnConBaths = OrderedDict(zip(ls, dnConBaths)) # -- Spectra information -- # Energy cut in eV. energy_cut *= k_B * T # XAS parameters # Energy-mesh w = np.linspace(-25, 25, 3000) # Each element is a XAS polarization vector. epsilons = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] # [[0,0,1]] # RIXS parameters # Polarization vectors, of in and outgoing photon. epsilonsRIXSin = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] # [[0,0,1]] epsilonsRIXSout = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] # [[0,0,1]] wIn = np.linspace(-10, 20, 50) wLoss = np.linspace(-2, 12, 4000) # NIXS parameters qsNIXS = [ 2 * np.array([1, 1, 1]) / np.sqrt(3), 7 * np.array([1, 1, 1]) / np.sqrt(3), ] # Angular momentum of final and initial orbitals in the NIXS excitation process. liNIXS, ljNIXS = 2, 2 # -- Occupation restrictions for excited states -- l = 2 restrictions = {} # Restriction on impurity orbitals indices = frozenset(c2i(nBaths, (l, s, m)) for s in range(2) for m in range(-l, l + 1)) restrictions[indices] = (n0imps[l] - 1, n0imps[l] + dnTols[l] + 1) # Restriction on valence bath orbitals indices = [] for b in range(nValBaths[l]): indices.append(c2i(nBaths, (l, b))) restrictions[frozenset(indices)] = (nValBaths[l] - dnValBaths[l], nValBaths[l]) # Restriction on conduction bath orbitals indices = [] for b in range(nValBaths[l], nBaths[l]): indices.append(c2i(nBaths, (l, b))) restrictions[frozenset(indices)] = (0, dnConBaths[l]) # Read the radial part of correlated orbitals radialMesh, RiNIXS = np.loadtxt(radial_filename).T RjNIXS = np.copy(RiNIXS) # Total number of spin-orbitals in the system n_spin_orbitals = sum(2 * (2 * ang + 1) + nBath for ang, nBath in nBaths.items()) if rank == 0: print("#spin-orbitals:", n_spin_orbitals) # Hamiltonian if rank == 0: print("Construct the Hamiltonian operator...") hOp = get_hamiltonian_operator_using_CF( nBaths, nValBaths, [Fdd, Fpp, Fpd, Gpd], [xi_2p, xi_3d], [n0imps, chargeTransferCorrection], hField, h0_CF_filename, ) # Measure how many physical processes the Hamiltonian contains. if rank == 0: print("{:d} processes in the Hamiltonian.".format(len(hOp))) # Many body basis for the ground state if rank == 0: print("Create basis...") basis = finite.get_basis(nBaths, nValBaths, dnValBaths, dnConBaths, dnTols, n0imps) if rank == 0: print("#basis states = {:d}".format(len(basis))) # Diagonalization of restricted active space Hamiltonian es, psis = finite.eigensystem(n_spin_orbitals, hOp, basis, nPsiMax) if rank == 0: print("time(ground_state) = {:.2f} seconds \n".format(time.time() - t0)) t0 = time.time() # Calculate static expectation values finite.printThermalExpValues(nBaths, es, psis) finite.printExpValues(nBaths, es, psis) # Print Slater determinants and weights if rank == 0: print("Slater determinants/product states and correspoinding weights") weights = [] for i, psi in enumerate(psis): print("Eigenstate {:d}.".format(i)) print("Consists of {:d} product states.".format(len(psi))) ws = np.array([abs(a) ** 2 for a in psi.values()]) s = np.array(list(psi.keys())) j = np.argsort(ws) ws = ws[j[-1::-1]] s = s[j[-1::-1]] weights.append(ws) if nPrintSlaterWeights > 0: print("Highest (product state) weights:") print(ws[:nPrintSlaterWeights]) print("Corresponding product states:") print(s[:nPrintSlaterWeights]) print("") # Calculate density matrix if rank == 0: print("Density matrix (in cubic harmonics basis):") for i, psi in enumerate(psis): print("Eigenstate {:d}".format(i)) n = finite.getDensityMatrixCubic(nBaths, psi) print("#density matrix elements: {:d}".format(len(n))) for e, ne in n.items(): if abs(ne) > tolPrintOccupation: if e[0] == e[1]: print( "Diagonal: (i,s) =", e[0], ", occupation = {:7.2f}".format(ne), ) else: print("Off-diagonal: (i,si), (j,sj) =", e, ", {:7.2f}".format(ne)) print("") # Save some information to disk if rank == 0: # Most of the input parameters. Dictonaries can be stored in this file format. np.savez_compressed( "data", ls=ls, nBaths=nBaths, nValBaths=nValBaths, n0imps=n0imps, dnTols=dnTols, dnValBaths=dnValBaths, dnConBaths=dnConBaths, Fdd=Fdd, Fpp=Fpp, Fpd=Fpd, Gpd=Gpd, xi_2p=xi_2p, xi_3d=xi_3d, chargeTransferCorrection=chargeTransferCorrection, hField=hField, h0_CF_filename=h0_CF_filename, nPsiMax=nPsiMax, T=T, energy_cut=energy_cut, delta=delta, restrictions=restrictions, epsilons=epsilons, epsilonsRIXSin=epsilonsRIXSin, epsilonsRIXSout=epsilonsRIXSout, deltaRIXS=deltaRIXS, deltaNIXS=deltaNIXS, n_spin_orbitals=n_spin_orbitals, hOp=hOp, ) # Save some of the arrays. # HDF5-format does not directly support dictonaries. h5f = h5py.File("spectra.h5", "w") h5f.create_dataset("E", data=es) h5f.create_dataset("w", data=w) h5f.create_dataset("wIn", data=wIn) h5f.create_dataset("wLoss", data=wLoss) h5f.create_dataset("qsNIXS", data=qsNIXS) h5f.create_dataset("r", data=radialMesh) h5f.create_dataset("RiNIXS", data=RiNIXS) h5f.create_dataset("RjNIXS", data=RjNIXS) else: h5f = None if rank == 0: print("time(expectation values) = {:.2f} seconds \n".format(time.time() - t0)) # Consider from now on only eigenstates with low energy es = tuple(e for e in es if e - es[0] < energy_cut) psis = tuple(psis[i] for i in range(len(es))) if rank == 0: print("Consider {:d} eigenstates for the spectra \n".format(len(es))) spectra.simulate_spectra( es, psis, hOp, T, w, delta, epsilons, wLoss, deltaNIXS, qsNIXS, liNIXS, ljNIXS, RiNIXS, RjNIXS, radialMesh, wIn, deltaRIXS, epsilonsRIXSin, epsilonsRIXSout, restrictions, h5f, nBaths, ) print("Script finished for rank:", rank)
def getNIXSOperator(nBaths, q, li, lj, Ri, Rj, r, kmin=1): r""" Return non-resonant inelastic x-ray scattering transition operator :math:`\hat{T}`. :math:`\hat{T} = \sum_{i,j,\sigma} T_{i,j} \hat{c}_{i\sigma}^\dagger \hat{c}_{j\sigma}`, where :math:`T_{i,j} = \langle i | e^{i\mathbf{q}\cdot \mathbf{r}} | j \rangle`. The plane-wave is expanded in spherical harmonics. See PRL 99 257401 (2007) for more information. Parameters ---------- nBaths : Ordered dict angular momentum: number of bath states. q : list Photon scattering vector q = [qx,qy,qz] The change in photon momentum. li : int Angular momentum of the orbitals to excite into. lj : int Angular momentum of the orbitals to excite from. Ri : list Radial part of the orbital to excite into. Normalized such that the integral of Ri^2(r) * r^2 should be equal to one. Rj : list Radial part of the orbital to excite from. Normalized such that the integral of Ri^2(r) * r^2 should be equal to one. r : list Radial mesh points. kmin : int The lowest integer in the plane-wave expansion. By default kmin = 1, which means that the monopole contribution is not included. To include also the monopole scattering, set kmin = 0. """ # Convert scattering list to numpy array q = np.array(q) qNorm = np.linalg.norm(q) # Polar (colatitudinal) coordinate theta = np.arccos(q[2] / qNorm) # Azimuthal (longitudinal) coordinate phi = np.arccos(q[0] / (qNorm * np.sin(theta))) tOp = {} for k in range(kmin, abs(li + lj) + 1): if (li + lj + k) % 2 == 0: Rintegral = np.trapz( np.conj(Ri) * spherical_jn(k, qNorm * r) * Rj * r**2, r) if rank == 0: print("Rintegral(k=", k, ") =", Rintegral) for mi in range(-li, li + 1): for mj in range(-lj, lj + 1): m = mi - mj if abs(m) <= k: tij = Rintegral tij *= 1j**(k) * sqrt(2 * k + 1) tij *= np.conj(sph_harm(m, k, phi, theta)) tij *= gauntC(k, li, mi, lj, mj, prec=16) if tij != 0: for s in range(2): i = c2i(nBaths, (li, s, mi)) j = c2i(nBaths, (lj, s, mj)) process = ((i, "c"), (j, "a")) if process in tOp: tOp[((i, "c"), (j, "a"))] += tij else: tOp[((i, "c"), (j, "a"))] = tij return tOp