def get_local_density_of_states(self,projected=False,width=0.05,window=None,npts=501): """ Return state density for all atoms as a function of energy. parameters: =========== projected: return local density of states projected for angular momenta 0,1 and 2 (s,p and d) ( sum of pldos over angular momenta = ldos ) width: energy broadening (in eV) window: energy window around Fermi-energy; 2-tuple (eV) npts: number of grid points for energy return: projected==False: energy grid, ldos[atom,grid] projected==True: energy grid, ldos[atom, grid], pldos[atom, angmom, grid] """ mn, mx = self.emin, self.emax if window is not None: mn, mx = window # only states within window kl,al,el = [],[],[] for k in range(self.nk): for a in range(self.norb): if mn<=self.e[k,a]<=mx: kl.append(k) al.append(a) el.append(self.e[k,a]) ldos = np.zeros((self.N,npts)) if projected: pldos = np.zeros((self.N,3,npts)) for i in range(self.N): q = [ self.get_atom_wf_mulliken(i,k,a,wk=True) for k,a in zip(kl,al) ] egrid, ldos[i,:] = mix.broaden( el,q,width=width,N=npts,a=mn,b=mx ) if projected: q = np.array( [self.get_atom_wf_all_angmom_mulliken(i,k,a,wk=True) for k,a in zip(kl,al)] ) for l in range(3): egrid, pldos[i,l] = mix.broaden( el,q[:,l],width=width,N=npts,a=mn,b=mx ) if projected: assert np.all( abs(ldos-pldos.sum(axis=1))<1E-6 ) return egrid, ldos, pldos else: return egrid, ldos
def plot_spectrum(self, filename, width=0.2, xlim=None): """ Make pretty plot of the linear response. Parameters: =========== filename: output file name (&format, supported by matplotlib) width: width of Lorenzian broadening xlim: energy range for plotting tuple (emin,emax) """ import pylab as pl if not self.done: self.run() e, f = mix.broaden(self.omega * Hartree, self.F, width=width, N=1000, extend=True) f = f / max(abs(f)) pl.plot(e, f, lw=2) xs, ys = pl.poly_between(e, 0, f) pl.fill(xs, ys, fc='b', ec='b', alpha=0.5) pl.ylim(0, 1.2) if xlim == None: pl.xlim(0, self.emax * Hartree * 1.2) else: pl.xlim(xlim) pl.xlabel('energy (eV)') pl.ylabel('linear optical response') pl.title('Optical response') pl.savefig(filename) #pl.show() pl.close()
def plot_spectrum(self, filename, width=0.2, xlim=None): """ Make pretty plot of the linear response. Parameters: =========== filename: output file name (&format, supported by matplotlib) width: width of Lorenzian broadening xlim: energy range for plotting tuple (emin,emax) """ import pylab as pl if not self.done: self.run() e, f = mix.broaden(self.omega * Hartree, self.F, width=width, N=1000, extend=True) f = f / max(abs(f)) pl.plot(e, f, lw=2) xs, ys = pl.poly_between(e, 0, f) pl.fill(xs, ys, fc="b", ec="b", alpha=0.5) pl.ylim(0, 1.2) if xlim == None: pl.xlim(0, self.emax * Hartree * 1.2) else: pl.xlim(xlim) pl.xlabel("energy (eV)") pl.ylabel("linear optical response") pl.title("Optical response") pl.savefig(filename) # pl.show() pl.close()
def get_density_of_states(self, broaden=False, projected=False, occu=False, width=0.05, window=None, npts=501): """ Return the full density of states. Sum of states over k-points. Zero is the Fermi-level. Spin-degeneracy is NOT counted. parameters: =========== broaden: * If True, return broadened DOS in regular grid in given energy window. * If False, return energies of all states, followed by their k-point weights. projected: project DOS for angular momenta occu: for not broadened case, return also state occupations width: Gaussian broadening (eV) window: energy window around Fermi-energy; 2-tuple (eV) npts: number of data points in output return: * if projected: e[:],dos[:],pdos[l,:] (angmom l=0,1,2) * if not projected: e[:],dos[:] * if broaden: e[:] is on regular grid, otherwise e[:] are eigenvalues and dos[...] corresponding weights * if occu: e[:],dos[:],occu[:] """ if broaden and occu or projected and occu: raise AssertionError( 'Occupation numbers cannot be returned with broadened DOS.') if projected: e, dos, pdos = self.get_local_density_of_states( True, width, window, npts) return e, dos.sum(axis=0), pdos.sum(axis=0) mn, mx = self.emin, self.emax if window is not None: mn, mx = window x, y, f = [], [], [] for a in range(self.calc.el.norb): x = np.concatenate((x, self.e[:, a])) y = np.concatenate((y, self.calc.st.wk)) f = np.concatenate((f, self.calc.st.f[:, a])) x = np.array(x) y = np.array(y) f = np.array(f) if broaden: e, dos = mix.broaden(x, y, width=width, N=npts, a=mn, b=mx) else: e, dos = x, y if occu: return e, dos, f else: return e, dos
def get_density_of_states(self,broaden=False,projected=False,occu=False,width=0.05,window=None,npts=501): """ Return the full density of states. Sum of states over k-points. Zero is the Fermi-level. Spin-degeneracy is NOT counted. parameters: =========== broaden: * If True, return broadened DOS in regular grid in given energy window. * If False, return energies of all states, followed by their k-point weights. projected: project DOS for angular momenta occu: for not broadened case, return also state occupations width: Gaussian broadening (eV) window: energy window around Fermi-energy; 2-tuple (eV) npts: number of data points in output return: * if projected: e[:],dos[:],pdos[l,:] (angmom l=0,1,2) * if not projected: e[:],dos[:] * if broaden: e[:] is on regular grid, otherwise e[:] are eigenvalues and dos[...] corresponding weights * if occu: e[:],dos[:],occu[:] """ if broaden and occu or projected and occu: raise AssertionError('Occupation numbers cannot be returned with broadened DOS.') if projected: e,dos,pdos = self.get_local_density_of_states(True,width,window,npts) return e,dos.sum(axis=0),pdos.sum(axis=0) mn, mx = self.emin, self.emax if window is not None: mn, mx = window x, y, f = [],[],[] for a in range(self.calc.el.norb): x = np.concatenate( (x,self.e[:,a]) ) y = np.concatenate( (y,self.calc.st.wk) ) f = np.concatenate( (f,self.calc.st.f[:,a]) ) x=np.array(x) y=np.array(y) f=np.array(f) if broaden: e,dos = mix.broaden(x, y, width=width, N=npts, a=mn, b=mx) else: e,dos = x,y if occu: return e,dos,f else: return e,dos
def get_dielectric_function(self,width=0.05,cutoff=None,N=400): """ Return the imaginary part of the dielectric function for non-SCC. Note: Uses approximation that requires that the orientation of neighboring unit cells does not change much. (Exact for Bravais lattice.) See, e.g., Marder, Condensed Matter Physics, or Popov New J. Phys 6, 17 (2004) parameters: ----------- width: energy broadening in eV cutoff: cutoff energy in eV N: number of points in energy grid return: ------- e[:], d[:,0:2] """ self.start_timing('dielectric function') width = width/Hartree otol = 0.05 # tolerance for occupations if cutoff==None: cutoff = 1E10 else: cutoff = cutoff/Hartree st = self.st nk, e, f, wk = st.nk, st.e, st.f, st.wk ex, wt = [], [] for k in range(nk): wf = st.wf[k] wfc = wf.conjugate() dS = st.dS[k].transpose((0,2,1)) ek = e[k] fk = f[k] kweight = wk[k] # electron excitation ka-->kb; restrict the search: bmin = list(fk<2-otol).index(True) amin = list(ek>ek[bmin]-cutoff).index(True) amax = list(fk<otol).index(True) for a in range(amin,amax+1): bmax = list(ek>ek[a]+cutoff).index(True) for b in range(max(a+1,bmin),bmax+1): de = ek[b]-ek[a] df = fk[a]-fk[b] if df<otol: continue # P = < ka | P | kb > P = 1j*hbar*np.dot(wfc[a],np.dot(dS,wf[b])) ex.append( de ) wt.append( kweight*df*np.abs(P)**2 ) ex, wt = np.array(ex), np.array(wt) cutoff = min( ex.max(),cutoff ) y = np.zeros((N,3)) for d in range(3): # Lorenzian should be used, but long tail would bring divergence at zero energy x,y[:,d] = broaden( ex,wt[:,d],width,'gaussian',N=N,a=width,b=cutoff ) y[:,d] = y[:,d]/x**2 const = (4*np.pi**2/hbar) self.stop_timing('dielectric function') return x*Hartree, y*const #y also in eV, Ang
from ase import * from pylab import * from hotbit import * from box import mix from hotbit.analysis import LinearResponse # equilibrium length Na2 = Atoms('Na2', [(0, 0, 0), (3.0, 0, 0)], cell=(6, 4, 4), pbc=False) Na2.center() calc = Hotbit(parameters=testpar, SCC=True, txt='optical.cal') Na2.set_calculator(calc) calc.solve_ground_state(Na2) lr = LinearResponse(calc, energy_cut=10, txt='lr.out') lr.run() lr.info() #get excitation energies and oscillator strengths omega, F = lr.get_linear_response() e, f = mix.broaden(omega, F, width=0.1, function='lorentzian') plot(e, f) savefig('lr.pdf')
from ase.units import Hartree Na3 = Atoms('Na3', positions=[(1.69649997, 0, 0), (-1.69649997, 0, 0), (0, 2.9384241, 0)], cell=(50, 50, 50), pbc=False) tm = mix.Timer() calc = Hotbit(charge=1, SCC=True, txt='test_lr.cal', **default_param) Na3.set_calculator(calc) calc.solve_ground_state(Na3) lr = LinearResponse(calc) omega, F = lr.get_linear_response() e, f = mix.broaden(omega, F, width=0.1) if plot: pl.scatter(e2, f2) pl.plot(e, f, label='python') pl.legend() pl.show() calc = Hotbit(SCC=True, txt='test_lr.cal', **default_param) C60 = Atoms(read('C60.xyz')) C60.set_calculator(calc) calc.solve_ground_state(C60) lr = LinearResponse(calc, energy_cut=10 / Hartree) omega, F = lr.get_linear_response() e, f = mix.broaden(omega, F, width=0.1)
import numpy as np from box import mix from ase.units import Hartree Na3=Atoms('Na3',positions=[(1.69649997,0,0),(-1.69649997,0,0),(0,2.9384241,0)],cell=(50,50,50),pbc=False) tm=mix.Timer() calc=Hotbit(charge=1,SCC=True,txt='test_lr.cal',**default_param) Na3.set_calculator(calc) calc.solve_ground_state(Na3) lr=LinearResponse(calc) omega,F=lr.get_linear_response() e,f=mix.broaden(omega,F,width=0.1) if plot: pl.scatter(e2,f2) pl.plot(e,f,label='python') pl.legend() pl.show() calc=Hotbit(SCC=True,txt='test_lr.cal',**default_param) C60=Atoms(read('C60.xyz')) C60.set_calculator(calc) calc.solve_ground_state(C60) lr=LinearResponse(calc,energy_cut=10/Hartree) omega,F=lr.get_linear_response() e,f=mix.broaden(omega,F,width=0.1)
def get_covalent_energy(self,mode='default',i=None,j=None,width=None,window=None,npts=501): """ Return covalent bond energies in different modes. (eV) ecov is described in Bornsen, Meyer, Grotheer, Fahnle, J. Phys.:Condens. Matter 11, L287 (1999) and Koskinen, Makinen Comput. Mat. Sci. 47, 237 (2009) parameters: =========== mode: 'default' total covalent energy 'orbitals' covalent energy for orbital pairs 'atoms' covalent energy for atom pairs 'angmom' covalent energy for angular momentum components i,j: atom or orbital indices, or angular momentum pairs width: * energy broadening (in eV) for ecov * if None, return energy eigenvalues and corresponding covalent energies in arrays, directly window: energy window (in eV wrt Fermi-level) for broadened ecov npts: number of points in energy grid (only with broadening) return: ======= x,y: * if width==None, x is list of energy eigenvalues (including k-points) and y covalent energies of those eigenstates * if width!=None, x is energy grid for ecov. * energies (both energy grid and ecov) are in eV. Note: energies are always shifted so that Fermi-level is at zero. Occupations are not otherwise take into account (while k-point weights are) """ eps = 1E-6 x, y = [], [] wf = self.st.wf energy = self.st.e - self.st.occu.get_mu() if window==None: mn,mx = energy.flatten().min()-eps, energy.flatten().max()+eps else: mn,mx = window[0]/Hartree, window[1]/Hartree if mode=='angmom': lorbs = [[],[],[]] for m,orb in enumerate(self.calc.el.orbitals()): lorbs[orb['angmom']].append(m) oi, oj = np.array(lorbs[i]), np.array(lorbs[j]) elif mode=='atoms': o1i, noi = self.calc.el.get_property_lists(['o1','no'])[i] o1j, noj = self.calc.el.get_property_lists(['o1','no'])[j] for k,wk in enumerate(self.wk): for a in range(self.norb): if not mn<=energy[k,a]<=mx: continue x.append( energy[k,a] ) if mode == 'default': e = 0.0 for m in range(self.norb): e += wk*np.sum( (wf[k,a,m]*wf[k,a,:].conj()*self.HS[k,:,m]) ) y.append(e) elif mode == 'orbitals': if i!=j: y.append( wk*2*(wf[k,a,i]*wf[k,a,j].conj()*self.HS[k,j,i]).real ) else: y.append( wk*wf[k,a,i]*wf[k,a,j].conj()*self.HS[k,j,i]) elif mode == 'atoms': e = 0.0 if i!=j: for m in range(o1i,o1i+noi): for n in range(o1j,o1j+noj): e += wk*2*(wf[k,a,m]*wf[k,a,n].conj()*self.HS[k,n,m]).real else: for m in range(o1i,o1i+noi): for n in range(o1j,o1j+noj): e += wk*(wf[k,a,m]*wf[k,a,n].conj()*self.HS[k,n,m]) y.append(e) elif mode == 'angmom': e = 0.0 for m in lorbs[i]: e += wk*np.sum( wf[k,a,m]*wf[k,a,oj].conj()*self.HS[k,oj,m] ) if i!=j: e += e.conj() y.append(e) else: raise NotImplementedError('Unknown covalent energy mode "%s".' %mode) x,y = np.array(x), np.array(y) assert np.all( abs(y.imag)<1E-12 ) y=y.real if width==None: return x * Hartree, y * Hartree else: e,ecov = mix.broaden(x, y, width=width/Hartree, N=npts, a=mn, b=mx) return e * Hartree, ecov * Hartree
from ase import * from pylab import * from hotbit import * from box import mix from hotbit.analysis import LinearResponse # equilibrium length Na2=Atoms('Na2',[(0,0,0),(3.0,0,0)],cell=(6,4,4),pbc=False) Na2.center() calc=Hotbit(parameters=testpar,SCC=True,txt='optical.cal') Na2.set_calculator(calc) calc.solve_ground_state(Na2) lr=LinearResponse(calc,energy_cut=10,txt='lr.out') lr.run() lr.info() #get excitation energies and oscillator strengths omega, F=lr.get_linear_response() e,f=mix.broaden(omega,F,width=0.1,function='lorentzian') plot(e,f) savefig('lr.pdf')
def get_dielectric_function(self, width=0.05, cutoff=None, N=400): """ Return the imaginary part of the dielectric function for non-SCC. Note: Uses approximation that requires that the orientation of neighboring unit cells does not change much. (Exact for Bravais lattice.) See, e.g., Marder, Condensed Matter Physics, or Popov New J. Phys 6, 17 (2004) parameters: ----------- width: energy broadening in eV cutoff: cutoff energy in eV N: number of points in energy grid return: ------- e[:], d[:,0:2] """ self.start_timing("dielectric function") width = width / Hartree otol = 0.05 # tolerance for occupations if cutoff == None: cutoff = 1e10 else: cutoff = cutoff / Hartree st = self.st nk, e, f, wk = st.nk, st.e, st.f, st.wk ex, wt = [], [] for k in range(nk): wf = st.wf[k] wfc = wf.conjugate() dS = st.dS[k].transpose((0, 2, 1)) ek = e[k] fk = f[k] kweight = wk[k] # electron excitation ka-->kb; restrict the search: bmin = list(fk < 2 - otol).index(True) amin = list(ek > ek[bmin] - cutoff).index(True) amax = list(fk < otol).index(True) for a in xrange(amin, amax + 1): bmax = list(ek > ek[a] + cutoff).index(True) for b in range(max(a + 1, bmin), bmax + 1): de = ek[b] - ek[a] df = fk[a] - fk[b] if df < otol: continue # P = < ka | P | kb > P = 1j * hbar * np.dot(wfc[a], np.dot(dS, wf[b])) ex.append(de) wt.append(kweight * df * np.abs(P) ** 2) ex, wt = np.array(ex), np.array(wt) cutoff = min(ex.max(), cutoff) y = np.zeros((N, 3)) for d in range(3): # Lorenzian should be used, but long tail would bring divergence at zero energy x, y[:, d] = broaden(ex, wt[:, d], width, "gaussian", N=N, a=width, b=cutoff) y[:, d] = y[:, d] / x ** 2 const = 4 * np.pi ** 2 / hbar self.stop_timing("dielectric function") return x * Hartree, y * const # y also in eV, Ang
def get_covalent_energy(self, mode='default', i=None, j=None, width=None, window=None, npts=501): """ Return covalent bond energies in different modes. (eV) ecov is described in Bornsen, Meyer, Grotheer, Fahnle, J. Phys.:Condens. Matter 11, L287 (1999) and Koskinen, Makinen Comput. Mat. Sci. 47, 237 (2009) parameters: =========== mode: 'default' total covalent energy 'orbitals' covalent energy for orbital pairs 'atoms' covalent energy for atom pairs 'angmom' covalent energy for angular momentum components i,j: atom or orbital indices, or angular momentum pairs width: * energy broadening (in eV) for ecov * if None, return energy eigenvalues and corresponding covalent energies in arrays, directly window: energy window (in eV wrt Fermi-level) for broadened ecov npts: number of points in energy grid (only with broadening) return: ======= x,y: * if width==None, x is list of energy eigenvalues (including k-points) and y covalent energies of those eigenstates * if width!=None, x is energy grid for ecov. * energies (both energy grid and ecov) are in eV. Note: energies are always shifted so that Fermi-level is at zero. Occupations are not otherwise take into account (while k-point weights are) """ eps = 1E-6 x, y = [], [] wf = self.st.wf energy = self.st.e - self.st.occu.get_mu() if window == None: mn, mx = energy.flatten().min() - eps, energy.flatten().max() + eps else: mn, mx = window[0] / Hartree, window[1] / Hartree if mode == 'angmom': lorbs = [[], [], []] for m, orb in enumerate(self.calc.el.orbitals()): lorbs[orb['angmom']].append(m) oi, oj = np.array(lorbs[i]), np.array(lorbs[j]) elif mode == 'atoms': o1i, noi = self.calc.el.get_property_lists(['o1', 'no'])[i] o1j, noj = self.calc.el.get_property_lists(['o1', 'no'])[j] for k, wk in enumerate(self.wk): for a in range(self.norb): if not mn <= energy[k, a] <= mx: continue x.append(energy[k, a]) if mode == 'default': e = 0.0 for m in range(self.norb): e += wk * np.sum((wf[k, a, m] * wf[k, a, :].conj() * self.HS[k, :, m])) y.append(e) elif mode == 'orbitals': if i != j: y.append(wk * 2 * (wf[k, a, i] * wf[k, a, j].conj() * self.HS[k, j, i]).real) else: y.append(wk * wf[k, a, i] * wf[k, a, j].conj() * self.HS[k, j, i]) elif mode == 'atoms': e = 0.0 if i != j: for m in range(o1i, o1i + noi): for n in range(o1j, o1j + noj): e += wk * 2 * (wf[k, a, m] * wf[k, a, n].conj() * self.HS[k, n, m]).real else: for m in range(o1i, o1i + noi): for n in range(o1j, o1j + noj): e += wk * (wf[k, a, m] * wf[k, a, n].conj() * self.HS[k, n, m]) y.append(e) elif mode == 'angmom': e = 0.0 for m in lorbs[i]: e += wk * np.sum(wf[k, a, m] * wf[k, a, oj].conj() * self.HS[k, oj, m]) if i != j: e += e.conj() y.append(e) else: raise NotImplementedError( 'Unknown covalent energy mode "%s".' % mode) x, y = np.array(x), np.array(y) assert np.all(abs(y.imag) < 1E-12) y = y.real if width == None: return x * Hartree, y * Hartree else: e, ecov = mix.broaden(x, y, width=width / Hartree, N=npts, a=mn, b=mx) return e * Hartree, ecov * Hartree
def get_local_density_of_states(self, projected=False, width=0.05, window=None, npts=501): """ Return state density for all atoms as a function of energy. parameters: =========== projected: return local density of states projected for angular momenta 0,1 and 2 (s,p and d) ( sum of pldos over angular momenta = ldos ) width: energy broadening (in eV) window: energy window around Fermi-energy; 2-tuple (eV) npts: number of grid points for energy return: projected==False: energy grid, ldos[atom,grid] projected==True: energy grid, ldos[atom, grid], pldos[atom, angmom, grid] """ mn, mx = self.emin, self.emax if window is not None: mn, mx = window # only states within window kl, al, el = [], [], [] for k in range(self.nk): for a in range(self.norb): if mn <= self.e[k, a] <= mx: kl.append(k) al.append(a) el.append(self.e[k, a]) ldos = np.zeros((self.N, npts)) if projected: pldos = np.zeros((self.N, 3, npts)) for i in range(self.N): q = [ self.get_atom_wf_mulliken(i, k, a, wk=True) for k, a in zip(kl, al) ] egrid, ldos[i, :] = mix.broaden(el, q, width=width, N=npts, a=mn, b=mx) if projected: q = np.array([ self.get_atom_wf_all_angmom_mulliken(i, k, a, wk=True) for k, a in zip(kl, al) ]) for l in range(3): egrid, pldos[i, l] = mix.broaden(el, q[:, l], width=width, N=npts, a=mn, b=mx) if projected: assert np.all(abs(ldos - pldos.sum(axis=1)) < 1E-6) return egrid, ldos, pldos else: return egrid, ldos