예제 #1
0
 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
예제 #2
0
파일: lr.py 프로젝트: pastewka/hotbit
    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()
예제 #3
0
파일: lr.py 프로젝트: pekkosk/hotbit
    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()
예제 #4
0
파일: mulliken.py 프로젝트: pastewka/hotbit
    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
예제 #5
0
 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
예제 #6
0
    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
예제 #7
0
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')
예제 #8
0
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)
예제 #9
0
    
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)
예제 #10
0
 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
예제 #11
0
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')

예제 #12
0
    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
예제 #13
0
파일: mulliken.py 프로젝트: pastewka/hotbit
    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
예제 #14
0
파일: mulliken.py 프로젝트: pastewka/hotbit
    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