def get_occupations(self, nel, width=None, refresh=False): """ calculate occupations of each eigenvalue. the the shape of the occupation is the same as self._eigenvals. [eig_k1,eigk2,...], each eig_k is a column with the length=nbands. if nspin=2 and fix_spin, there are two fermi energies. NOTE: this conflicts with the DOS caluculation. FIXME. :param nel: number of electrons. if fix_spin, the nel is a tuple of (nel_up,nel_dn) :Returns: self._occupations (np.ndarray) index:[band,kpt,orb,spin] if nspin==2 else [band,kpt,orb] same as eigenvec """ if self._verbose: print("Calc occupation") self._nel = nel self.get_eigenvalues(refresh=refresh) #print self._kweights #print self._eigenvals occ = Occupations(nel, self._width, self._kweights, nspin=self._nspin) self._occupations = occ.occupy(self._eigenvals) self._efermi = occ.get_mu() if self._verbose: print("End Calc occupation")
def get_occupations(self, nel, width=0.2, refresh=False): """ calculate occupations of each eigenvalue. the the shape of the occupation is the same as self._eigenvals. [eig_k1,eigk2,...], each eig_k is a column with the length=nbands. if nspin=2 and fix_spin, there are two fermi energies. NOTE: this conflicts with the DOS caluculation. FIXME. :param nel: number of electrons. if fix_spin, the nel is a tuple of (nel_up,nel_dn) :Returns: self._occupations (np.ndarray) index:[band,kpt,orb,spin] if nspin==2 else [band,kpt,orb] same as eigenvec """ self._nel = nel self.get_eigenvalues(refresh=refresh) #print self._kweights #print self._eigenvals if self._nspin == 1 or not self.fix_spin: occ = Occupations(nel, width, self._kweights, nspin=self._nspin) self._occupations = occ.occupy(self._eigenvals) self._efermi = occ.get_mu() elif self._nspin == 2 and self.fix_spin: raise NotImplementedError( "current implement on fix_spin is not correct.") u"""FIXME: 这根本就不对,eig无法被直接区分为eig_up,eig_dn,不能这样处理""" nel_up, nel_dn = nel eig_up = self.eigenvals[::2] eig_dn = self.eigenvals[1::2] occ_up = Occupations(nel_up, width, self._kweights, nspin=1) occupations_up = occ_up.occupy(eig_up) efermi_up = occ_up.get_mu() occ_dn = Occupations(nel_dn, width, self._kweights, nspin=1) occupations_dn = occ_dn.occupy(eig_dn) efermi_dn = occ_dn.get_mu() self._occupations[::2] = occupations_up self._occupations[1::2] = occupations_dn self.efermi = (efermi_up, efermi_dn) return self._occupations
def get_occupations(self, nel=None, width=0.05, refresh=False): """ calculate occupations of each eigenvalue. the the shape of the occupation is the same as self._eigenvals. [eig_k1,eigk2,...], each eig_k is a column with the length=nbands. :param nel: number of electrons. :Returns: self._occupations (np.ndarray) index:[band,kpt,orb,spin] if nspin==2 else [band,kpt,orb] same as eigenvec """ if nel is not None: self._nel = nel nel = self._nel self.get_eigenvalues(refresh=refresh) #print self._kweights #print self._eigenvals if self._nspin == 1: occ = Occupations(nel, width, self._kweights, nspin=self._nspin) self._occupations = occ.occupy(self._eigenvals) self._efermi = occ.get_mu() elif self._nspin == 2: raise NotImplementedError( "current implement on fix_spin is not correct.") nel_up, nel_dn = nel eig_up = self.eigenvals[::2] eig_dn = self.eigenvals[1::2] occ_up = Occupations(nel_up, width, self._kweights, nspin=1) occupations_up = occ_up.occupy(eig_up) efermi_up = occ_up.get_mu() occ_dn = Occupations(nel_dn, width, self._kweights, nspin=1) occupations_dn = occ_dn.occupy(eig_dn) efermi_dn = occ_dn.get_mu() self._occupations[::2] = occupations_up self._occupations[1::2] = occupations_dn self.efermi = (efermi_up, efermi_dn) return self._occupations
class States: def __init__(self,calc): self.es = Electrostatics(calc, charge_density = calc.get('charge_density'), solver = calc.get('coulomb_solver')) self.solver=Solver(calc) self.calc=proxy(calc) self.nat=len(calc.el) self.norb=calc.el.get_nr_orbitals() self.prev_dq=[None,None] self.count=0 self.first_solve = True self.SCC=calc.get('SCC') self.rho=None self.rhoe0=None self.nk=None def setup_k_sampling(self,kpts,physical=True,rs='kappa'): ''' Setup the k-point sampling and their weights. @param kpts: 3-tuple: number of k-points in different directions list of 3-tuples: k-points given explicitly @param physical: No meaning for infinite periodicities. For physically periodic systems only certain number of k-points are allowed. (like wedge of angle 2*pi/N, only number of k-points that divides N, is physically allowed). If physical=False, allow interpolation of this k-sampling. rs: use 'kappa'- or 'k'-point sampling ''' if (len(kpts)==1 or isinstance(kpts,tuple) and kpts!=(1,1,1)) and self.calc.get('width')<1E-10: raise AssertionError('With k-point sampling width must be>0!') M = self.calc.el.get_number_of_transformations() if isinstance(kpts,tuple): table = self.calc.el.atoms.container.get_table() # set up equal-weighted and spaced k-point mesh if 0 in kpts: raise AssertionError('Each direction must have at least one k-point! (Gamma-point)') kl=[] for i in range(3): if M[i]==np.Inf: # arbitrary sampling is allowed spacing = 2*pi/kpts[i] kl.append( np.linspace(-pi+spacing/2,pi-spacing/2,kpts[i]) ) else: # TODO: choose the closest number of k-points if # sampling should be physical # first calculate possible numer of k-points, then # select the ones closest to the desired one # nks = M/divisors(M) # nk1 = nks[abs(nks-nk).argmin()] # discrete, well-defined sampling; any k-point is not allowed if kpts[i] not in mix.divisors(M[i]) and physical: print 'Allowed k-points for direction',i,'are',mix.divisors(M[i]) raise Warning('Non-physical k-point sampling! ') else: kl.append( np.linspace(0,2*pi-2*pi/kpts[i],kpts[i]) ) k=[] wk=[] kpt_indices = [] nk0 = np.prod(kpts) for a in range(kpts[0]): for b in range(kpts[1]): for c in range(kpts[2]): newk = np.array([kl[0][a], kl[1][b], kl[2][c]]) newind = (a,b,c) one_equivalent = False for i in range(3): if 'equivalent' in table[i]: if one_equivalent: raise NotImplementedError('Surprising type of new symmetry; reconsider implementation.') one_equivalent = True n = table[i]['equivalent'] # symmetry op i is equivalent to tuple n assert n[i]==0 newk[i] = newk[i] + 1.0*np.dot(n,newk)/M[i] inv_exists = False # if newk's inverse exists, increase its weight by default for ik, oldk in enumerate(k): if np.linalg.norm(oldk+newk)<1E-10: inv_exists = True wk[ik]+=1.0/nk0 # newk's inverse does not exist; make new k-point if not inv_exists: k.append( newk ) wk.append( 1.0/nk0 ) kpt_indices.append( newind ) nk=len(k) k=np.array(k) wk=np.array(wk) else: # work with a given set of k-points nk=len(kpts) if rs=='k': k = k_to_kappa_points(kpts,self.calc.el.atoms) else: k=np.array(kpts) wk=np.ones(nk)/nk kl=None kpt_indices=[] # now sampling is set up. Check consistency. pbc = self.calc.el.get_pbc() self.kpt_indices = np.array(kpt_indices) for i in range(3): for kp in k: if kp[i]>1E-10 and not pbc[i]: raise AssertionError('Do not set (non-zero) k-points in non-periodic direction!') return nk, k, kl, wk def guess_dq(self): n=len(self.calc.el) if not self.SCC: return np.zeros((n,)) if self.count==0: return np.zeros((n,)) - float(self.calc.get('charge'))/n elif self.count==1: # use previous charges return self.prev_dq[0] else: # linear extrapolation return self.prev_dq[0] + (self.prev_dq[0]-self.prev_dq[1]) def solve(self): if self.nk==None: physical = self.calc.get('physical_k') self.nk, self.k, self.kl, self.wk = self.setup_k_sampling( self.calc.get('kpts'),physical=physical,rs=self.calc.get('rs') ) width=self.calc.get('width') self.occu = Occupations(self.calc.el.get_number_of_electrons(),width,self.wk) self.calc.start_timing('solve') # TODO: enable fixed dq-calculations in SCC (for band-structures) dq=self.guess_dq() if self.first_solve: self.calc.memory_estimate() self.first_solve = False self.H0, self.S, self.dH0, self.dS = self.calc.ia.get_matrices() if self.SCC: self.es.construct_Gamma_matrix(self.calc.el.atoms) self.e, self.wf = self.solver.get_states(self.calc,dq,self.H0,self.S) self.check_mulliken_charges() self.large_update() self.count+=1 self.calc.stop_timing('solve') def check_mulliken_charges(self): """ Check that the Mulliken populations are physically reasonable. """ dQ = self.mulliken() if self.calc.verbose_SCC: print>> self.calc.get_output(), "Mulliken populations: min=%0.3f, max=%0.3f" % (np.min(dQ),np.max(dQ)) Z = self.calc.el.get_atomic_numbers() for dq, z in zip(dQ, Z): if dq < -z or dq > z: for dq, z in zip(dQ, Z): print >> self.calc.get_output(), "Z=%i dq=%0.3f excess charge=%0.3f" % (z, dq, -dq) raise Exception("The Mulliken charges are insane!") def update(self,e,wf): """ Update all essential stuff from given wave functions. """ self.calc.start_timing('update') self.e=e self.wf=wf self.f=self.occu.occupy(e) self.calc.start_timing('rho') self.rho = compute_rho(self.wf,self.f) self.calc.stop_timing('rho') if self.SCC: self.dq = self.mulliken() self.es.set_dq(self.dq) self.calc.stop_timing('update') def large_update(self): """ Update stuff from eigenstates needed later for forces etc. """ self.calc.start_timing('final update') self.Swf = None self.dH = np.zeros_like(self.dH0) if self.SCC: self.prev_dq=[self.dq, self.prev_dq[0]] # TODO: do k-sum with numpy for ik in range(self.nk): for a in range(3): self.dH[ik,:,:,a] = self.dH0[ik,:,:,a] + self.es.get_h1()*self.dS[ik,:,:,a] else: self.dH = self.dH0 # density matrix weighted by eigenenergies self.rhoe = compute_rhoe(self.wf,self.f,self.e) self.calc.stop_timing('final update') def get_dq(self): return self.dq.copy() def get_eigenvalues(self): return self.e.copy() def get_band_energies(self, kpts, h1=False): """ Return the eigenvalue spectrum for a set of explicitly given k-points. """ H0, S, dH0, dS = self.calc.ia.get_matrices(kpts) H1 = None if h1: H1 = self.es.get_h1() e, wf = self.solver.get_eigenvalues_and_wavefunctions(H0, S, H1=H1) return e def get_occupations(self): return self.f.copy() def get_homo(self,occu=0.99): """ Return highest *largely* occupied orbital (>occu) 0<=occu<=2. Recommended use only for molecules. """ for i in range(self.norb)[::-1]: if np.any( self.f[:,i]>occu ): return i def get_lumo(self,occu=1.01): """ Return lowest *largely* unuccopied orbital (<occu) 0<=occu<=2. Recommended use only for molecules. """ for i in range(self.norb): if np.any( self.f[:,i]<occu ): return i def mulliken(self): ''' Return excess Mulliken populations dq = dq(total)-dq0 dq_I = sum_k w_k Trace_I Re[ rho(k)*S(k) ] = sum_(i in I) Re [ sum_k w_k sum_j rho(k)_ij*S(k)_ji ] = sum_(i in I) Re [ sum_k w_k sum_j rho(k)_ij*S(k)^T_ij ] = sum_(i in I) [ sum_k w_k diag(k)_i ], = sum_(i in I) diag_i where diag(k)_i = Re [sum_j rho(k)_ij * S(k)^T_ij] and diag_i = sum_k w_k diag(k)_i ''' diag = np.zeros((self.norb)) for ik in xrange(self.nk): diag_k = ( self.rho[ik]*self.S[ik].transpose() ).sum(axis=1).real diag = diag + self.wk[ik] * diag_k q=[] for o1, no in self.calc.el.get_property_lists(['o1','no']): q.append( diag[o1:o1+no].sum() ) dq = np.array(q)-self.calc.el.get_valences() q,c = sum(-dq),self.calc.get('charge') if np.abs(q-c) > self.calc.get('tol_mulliken'): raise RuntimeError('Mulliken charges (%.4f) do not add up to total charge (%.4f)!' %(q,c)) return dq def get_band_structure_energy(self): ''' Return band structure energy. ebs = sum_k w_k ( sum_ij rho_ij * H0_ji ) = sum_k w_k ( sum_i [sum_j rho_ij * H0^T_ij] ) ''' self.calc.start_timing('e_bs') ebs = 0.0 for ik in xrange(self.nk): diagonal = ( self.rho[ik]*self.H0[ik].transpose() ).sum(axis=1) ebs += self.wk[ik] * diagonal.sum() assert ebs.imag<self.calc.get('tol_imaginary_e') self.calc.stop_timing('e_bs') return ebs.real def get_band_structure_forces(self): ''' Return band structure forces. F_I = - sum_k w_k Trace_I [ dH(k)*rho(k) - dS(k)*rhoe(k) + c.c ] = - sum_k w_k sum_(i in I) diag_i(k) + c.c., where diag_i(k) = [dH(k)*rho(k) - dS(k)*rhoe(k)]_ii = sum_j [dH(k)_ij*rho(k)_ji - dS(k)_ij*rhoe(k)_ji] = sum_j [dH(k)_ij*rho(k)^T_ij - dS(k)_ij*rhoe(k)^T_ij] ''' self.calc.start_timing('f_bs') diag = np.zeros((self.norb,3),complex) for a in range(3): for ik in range(self.nk): diag_k = ( self.dH[ik,:,:,a]*self.rho[ik].transpose() \ - self.dS[ik,:,:,a]*self.rhoe[ik].transpose() ).sum(axis=1) diag[:,a] = diag[:,a] - self.wk[ik] * diag_k f=[] for o1, no in self.calc.el.get_property_lists(['o1','no']): f.append( 2*diag[o1:o1+no,:].sum(axis=0).real ) self.calc.stop_timing('f_bs') return f