def __init__(self, calc, mm, mp, dipoles, finegrid=False): #quadruoles # Need QM density (on grid) and MM object for CM self.calc = calc # QM calculator self.mm = mm # SCME atoms object self.mp = mp # no. atoms per SCME mol # # Interacting with self.dipoles = dipoles # self.qpoles = qpoles # # LOG self.timer = Timer() self.out = sys.stdout # self.cm = None
def __init__(self, calculator=None, **kwargs): self.timer = Timer() self.set(**kwargs) if isinstance(calculator, str): ExcitationList.__init__(self, None, self.txt) self.filename = calculator else: ExcitationList.__init__(self, calculator, self.txt) if self.filename is not None: return self.read(self.filename) if self.eh_comm is None: self.eh_comm = mpi.serial_comm elif isinstance(self.eh_comm, (mpi.world.__class__, mpi.serial_comm.__class__)): # Correct type already. pass else: # world should be a list of ranks: self.eh_comm = mpi.world.new_communicator(np.asarray(eh_comm)) if calculator is not None and calculator.initialized: if calculator.wfs.kpt_comm.size > 1: err_txt = "Spin parallelization with Linear response " err_txt += "TDDFT. Use parallel = {'domain' : 'domain_only'} " err_txt += "calculator parameter." raise NotImplementedError(err_txt) if self.xc == 'GS': self.xc = calculator.hamiltonian.xc.name calculator.converge_wave_functions() if calculator.density.nct_G is None: spos_ac = calculator.initialize_positions() calculator.wfs.initialize(calculator.density, calculator.hamiltonian, spos_ac) self.update(calculator)
def __init__(self, mm, qm, mp, calcmm, dyn=False, g=0.5): self.mm = mm # mm atoms object self.mp = mp # no. atoms per center of mass (cm) self.dyn = dyn # dyn. update of potential? self.calcmm = calcmm # mm calculator (SCME) self.qm = qm # qm atoms object # self.qmidx = len(qm)# no. QM atoms # self.nm = len(self.mm) / self.mp self.cm = self.get_cm(self.nm) self.timer = Timer() # smoothing value self.g = g # initialization self.initial = True # Hold on to old arrays self.dipoles = None self.qpoles = None self.dipoles_1 = None self.qpoles_1 = None
class LrTDDFT(ExcitationList): """Linear Response TDDFT excitation class Input parameters: calculator: the calculator object after a ground state calculation nspins: number of spins considered in the calculation Note: Valid only for unpolarised ground state calculation eps: Minimal occupation difference for a transition (default 0.001) istart: First occupied state to consider jend: Last unoccupied state to consider xc: Exchange-Correlation approximation in the Kernel derivative_level: 0: use Exc, 1: use vxc, 2: use fxc if available filename: read from a file """ def __init__(self, calculator=None, **kwargs): self.timer = Timer() self.set(**kwargs) if isinstance(calculator, str): ExcitationList.__init__(self, None, self.txt) self.filename = calculator else: ExcitationList.__init__(self, calculator, self.txt) if self.filename is not None: return self.read(self.filename) if self.eh_comm is None: self.eh_comm = mpi.serial_comm elif isinstance(self.eh_comm, (mpi.world.__class__, mpi.serial_comm.__class__)): # Correct type already. pass else: # world should be a list of ranks: self.eh_comm = mpi.world.new_communicator(np.asarray(eh_comm)) if calculator is not None and calculator.initialized: if calculator.wfs.kpt_comm.size > 1: err_txt = "Spin parallelization with Linear response " err_txt += "TDDFT. Use parallel = {'domain' : 'domain_only'} " err_txt += "calculator parameter." raise NotImplementedError(err_txt) if self.xc == 'GS': self.xc = calculator.hamiltonian.xc.name calculator.converge_wave_functions() if calculator.density.nct_G is None: spos_ac = calculator.initialize_positions() calculator.wfs.initialize(calculator.density, calculator.hamiltonian, spos_ac) self.update(calculator) def set(self, **kwargs): defaults = { 'nspins' : None, 'eps' : 0.001, 'istart' : 0, 'jend' : None, 'energy_range' : None, 'xc' : 'GS', 'derivative_level' : 1, 'numscale' : 0.00001, 'txt' : None, 'filename' : None, 'finegrid' : 2, 'force_ApmB' : False, # for tests 'eh_comm' : None # parallelization over eh-pairs } changed = False for key, value in defaults.items(): if hasattr(self, key): value = getattr(self, key) # do not overwrite setattr(self, key, kwargs.pop(key, value)) if value != getattr(self, key): changed = True for key in kwargs: raise KeyError('Unknown key ' + key) return changed def set_calculator(self, calculator): self.calculator = calculator # self.force_ApmB = parameters['force_ApmB'] self.force_ApmB = None # XXX def analyse(self, what=None, out=None, min=0.1): """Print info about the transitions. Parameters: 1. what: I list of excitation indicees, None means all 2. out : I where to send the output, None means sys.stdout 3. min : I minimal contribution to list (0<min<1) """ if what is None: what = range(len(self)) elif isinstance(what, int): what = [what] if out is None: out = sys.stdout for i in what: print >> out, str(i) + ':', self[i].analyse(min=min) def update(self, calculator=None, **kwargs): changed = self.set(**kwargs) if calculator is not None: changed = True self.set_calculator(calculator) if not changed: return self.forced_update() def forced_update(self): """Recalc yourself.""" nonselfconsistent_xc = None if not self.force_ApmB: Om = OmegaMatrix name = 'LrTDDFT' if self.xc: xc = XC(self.xc) if hasattr(xc, 'hybrid') and xc.hybrid > 0.0: Om = ApmB name = 'LrTDDFThyb' # nonselfconsistent_xc = HybridXC('PBE0', alpha=5.0) else: Om = ApmB name = 'LrTDDFThyb' self.kss = KSSingles(calculator=self.calculator, nspins=self.nspins, nonselfconsistent_xc=nonselfconsistent_xc, eps=self.eps, istart=self.istart, jend=self.jend, energy_range=self.energy_range, txt=self.txt) self.Om = Om(self.calculator, self.kss, self.xc, self.derivative_level, self.numscale, finegrid=self.finegrid, eh_comm=self.eh_comm, txt=self.txt) self.name = name def diagonalize(self, istart=None, jend=None, energy_range=None, TDA=False): self.timer.start('diagonalize') self.timer.start('omega') self.Om.diagonalize(istart, jend, energy_range, TDA) self.timer.stop('omega') # remove old stuff self.timer.start('clean') while len(self): self.pop() self.timer.stop('clean') print >> self.txt, 'LrTDDFT digonalized:' self.timer.start('build') for j in range(len(self.Om.kss)): self.append(LrTDDFTExcitation(self.Om, j)) print >> self.txt, ' ', str(self[-1]) self.timer.stop('build') self.timer.stop('diagonalize') def get_Om(self): return self.Om def read(self, filename=None, fh=None): """Read myself from a file""" if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename) except: f = open(filename, 'r') else: f = open(filename, 'r') self.filename = filename else: f = fh self.filename = None # get my name s = f.readline().replace('\n','') self.name = s.split()[1] self.xc = f.readline().replace('\n','').split()[0] values = f.readline().split() self.eps = float(values[0]) if len(values) > 1: self.derivative_level = int(values[1]) self.numscale = float(values[2]) self.finegrid = int(values[3]) else: # old writing style, use old defaults self.numscale = 0.001 self.kss = KSSingles(filehandle=f) if self.name == 'LrTDDFT': self.Om = OmegaMatrix(kss=self.kss, filehandle=f, txt=self.txt) else: self.Om = ApmB(kss=self.kss, filehandle=f, txt=self.txt) self.Om.Kss(self.kss) # check if already diagonalized p = f.tell() s = f.readline() if s != '# Eigenvalues\n': # go back to previous position f.seek(p) else: # load the eigenvalues n = int(f.readline().split()[0]) for i in range(n): self.append(LrTDDFTExcitation(string=f.readline())) # load the eigenvectors f.readline() for i in range(n): values = f.readline().split() weights = [float(val) for val in values] self[i].f = np.array(weights) self[i].kss = self.kss if fh is None: f.close() # update own variables self.istart = self.Om.fullkss.istart self.jend = self.Om.fullkss.jend def singlets_triplets(self): """Split yourself into a singlet and triplet object""" slr = LrTDDFT(None, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, xc=self.xc, derivative_level=self.derivative_level, numscale=self.numscale) tlr = LrTDDFT(None, nspins=self.nspins, eps=self.eps, istart=self.istart, jend=self.jend, xc=self.xc, derivative_level=self.derivative_level, numscale=self.numscale) slr.Om, tlr.Om = self.Om.singlets_triplets() for lr in [slr, tlr]: lr.kss = lr.Om.fullkss return slr, tlr def single_pole_approximation(self, i, j): """Return the excitation according to the single pole approximation. See e.g.: Grabo et al, Theochem 501 (2000) 353-367 """ for ij, kss in enumerate(self.kss): if kss.i == i and kss.j == j: return sqrt(self.Om.full[ij][ij]) * Hartree return self.Om.full[ij][ij] / kss.energy * Hartree def __str__(self): string = ExcitationList.__str__(self) string += '# derived from:\n' string += self.Om.kss.__str__() return string def write(self, filename=None, fh=None): """Write current state to a file. 'filename' is the filename. If the filename ends in .gz, the file is automatically saved in compressed gzip format. 'fh' is a filehandle. This can be used to write into already opened files. """ if mpi.rank == mpi.MASTER: if fh is None: if filename.endswith('.gz'): try: import gzip f = gzip.open(filename,'wb') except: f = open(filename, 'w') else: f = open(filename, 'w') else: f = fh f.write('# ' + self.name + '\n') xc = self.xc if xc is None: xc = 'RPA' if self.calculator is not None: xc += ' ' + self.calculator.get_xc_functional() f.write(xc + '\n') f.write('%g %d %g %d' % (self.eps, int(self.derivative_level), self.numscale, int(self.finegrid)) + '\n') self.kss.write(fh=f) self.Om.write(fh=f) if len(self): f.write('# Eigenvalues\n') istart = self.istart if istart is None: istart = self.kss.istart jend = self.jend if jend is None: jend = self.kss.jend f.write('%d %d %d'%(len(self), istart, jend) + '\n') for ex in self: f.write(ex.outstring()) f.write('# Eigenvectors\n') for ex in self: for w in ex.f: f.write('%g '%w) f.write('\n') if fh is None: f.close()
class DipoleDensityForces: def __init__(self, calc, mm, mp, dipoles, finegrid=False): #quadruoles # Need QM density (on grid) and MM object for CM self.calc = calc # QM calculator self.mm = mm # SCME atoms object self.mp = mp # no. atoms per SCME mol # # Interacting with self.dipoles = dipoles # self.qpoles = qpoles # # LOG self.timer = Timer() self.out = sys.stdout # self.cm = None def calculate_forces(self): self.out.write('Calculating E-dens to MM Forces') self.timer.start('E-dens to MM Forces') # if self.cm is None: self.get_cm() # Force array n = len(self.atoms) / self.mp F = np.zeros((3,n)) # calc = self.calc # Grab grid descriptor and density if self.finegd: gd = calc.density.finegd # Get dens on fg if calc.density.nt_sg is None: calc.density.interpolate_pseudo_density() nt_sg = calc.density.nt_sg # else: gd = calc.density.gd nt_sg = calc.density.nt_sG # if calc.density.nspins == 1: nt_g = nt_sg[0] else: nt_g = nt_sg.sum(axis=1) # sg = (np.indices(gd.n_c, float).T + \ gd.beg_c) / gd.N_c # for a, pos in enumerate(self.cm): # Get all scaled gpt distance to cm asg = sg - np.linalg.solve(gd.cell_cv.T, pos) # r(xyz) - in Ang xyz = np.dot(asg, gd.cell_cv) * Bohr # dis = np.sqrt(((xyz.T)**2).sum(axis=0)) # n(r)/d**3 n_d = nt_sg / dis**3 # p*r pr_r = xyz.T*np.dot(xyz, self.dipole[a]) # whole term (with electrostatic constant) tot = k_c * n_d * (self.dipole[a] - 3*pr_r.T).T # # new = tot.reshape((3,tot[1]*tot[2]*tot[3] in shape)) # F[:,a] = [tot[0].sum(), tot[1].sum(), tot[2].sum()] self.timer.stop('E-dens to MM Forces') self.out.write(' E-dens/MM Forces took: %.3f sec' %self.timer.timers[('E-dens to MM Forces',)]) return F def get_cm(self): """ Get CM for SCME mols in units [Bohr] """ # atoms = self.mm mp = self.mp n = len(self.atoms) / self.mp cm = np.zeros((n,3)) # for i in range(n): cm[i,:] += atoms[i*mp:(i+1)*mp].get_center_of_mass() / Bohr # self.cm = cm.copy()
def test_inv_speed(self): full_mat = self.recover() timer = Timer() timer.start('full_numpy') tmp0 = np.linalg.inv(full_mat) timer.stop('full_numpy') timer.start('full_lapack') inverse_general(full_mat) timer.stop('full_lapack') timer.start('sparse_lapack') self.inv_eq() timer.stop('sparse_lapack') timer.start('sparse_lapack_ne') self.inv_ne() timer.stop('sparse_lapack_ne') times = [] methods = ['full_numpy', 'full_lapack', 'sparse_lapack'] for name in methods: time = timer.timers[name,] print name, time times.append(time) mintime = np.min(times) self.inv_method = methods[np.argmin(times)] print 'mintime', mintime print 'sparse_lapack_ne', timer.timers['sparse_lapack_ne',]
import sys from ase import Atoms, Atom from gpaw import GPAW from gpaw.test import equal from gpaw.utilities.timing import Timer from gpaw.xc.hybrid import HybridXC timer = Timer() loa = Atoms('Be2', [(0, 0, 0), (2.45, 0, 0)], magmoms=[0.5, 0.5], cell=[5.9, 4.8, 5.0]) loa.center() fgl = [False, True] #fgl = [True, False] txt='-' txt='/dev/null' E = {} niter = {} for fg in fgl: if fg: tstr = 'Exx on fine grid' else: tstr = 'Exx on coarse grid' timer.start(tstr)
def get_rpa(self): """calculate RPA part of the omega matrix""" # shorthands kss=self.fullkss finegrid=self.finegrid wfs = self.paw.wfs eh_comm = self.eh_comm # calculate omega matrix nij = len(kss) print >> self.txt,'RPA',nij,'transitions' Om = self.Om for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt,'RPA kss['+'%d'%ij+']=', kss[ij] timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges( finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype.char) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() ## print "shapes 0=",phit.shape,rhot.shape self.restrict(phit_p,phit) else: phit = phit_p rhot = rhot_p for kq in range(ij,nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges( finegrid is 2) timer2.stop() pre = 2 * sqrt(kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight() ) I = self.Coulomb_integral_kss(kss[ij], kss[kq], rhot, phit, timer2) Om[ij,kq] = pre * I if ij == kq: Om[ij,kq] += kss[ij].get_energy()**2 else: Om[kq,ij]=Om[ij,kq] timer.stop() ## timer2.write() if ij < (nij-1): t = timer.get_time(ij) # time for nij-ij calculations t = .5*t*(nij-ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'RPA estimated time left',\ self.timestring(t0*(nij-ij-1)+t)
def get_xc(self): """Add xc part of the coupling matrix""" # shorthands paw = self.paw wfs = paw.wfs gd = paw.density.finegd comm = gd.comm eh_comm = self.eh_comm fg = self.finegrid is 2 kss = self.fullkss nij = len(kss) Om_xc = self.Om # initialize densities # nt_sg is the smooth density on the fine grid with spin index if kss.nvspins==2: # spin polarised ground state calc. nt_sg = paw.density.nt_sg else: # spin unpolarised ground state calc. if kss.npspins==2: # construct spin polarised densities nt_sg = np.array([.5*paw.density.nt_sg[0], .5*paw.density.nt_sg[0]]) else: nt_sg = paw.density.nt_sg # check if D_sp have been changed before D_asp = self.paw.density.D_asp for a, D_sp in D_asp.items(): if len(D_sp) != kss.npspins: if len(D_sp) == 1: D_asp[a] = np.array([0.5 * D_sp[0], 0.5 * D_sp[0]]) else: D_asp[a] = np.array([D_sp[0] + D_sp[1]]) # restrict the density if needed if fg: nt_s = nt_sg else: nt_s = self.gd.zeros(nt_sg.shape[0]) for s in range(nt_sg.shape[0]): self.restrict(nt_sg[s], nt_s[s]) gd = paw.density.gd # initialize vxc or fxc if self.derivativeLevel==0: raise NotImplementedError if kss.npspins==2: v_g=nt_sg[0].copy() else: v_g=nt_sg.copy() elif self.derivativeLevel==1: pass elif self.derivativeLevel==2: fxc_sg = np.zeros(nt_sg.shape) self.xc.calculate_fxc(gd, nt_sg, fxc_sg) else: raise ValueError('derivativeLevel can only be 0,1,2') ## self.paw.my_nuclei = [] ns=self.numscale xc=self.xc print >> self.txt, 'XC',nij,'transitions' for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt,'XC kss['+'%d'%ij+']' timer = Timer() timer.start('init') timer2 = Timer() if self.derivativeLevel >= 1: # vxc is available # We use the numerical two point formula for calculating # the integral over fxc*n_ij. The results are # vvt_s smooth integral # nucleus.I_sp atom based correction matrices (pack2) # stored on each nucleus timer2.start('init v grids') vp_s=np.zeros(nt_s.shape,nt_s.dtype.char) vm_s=np.zeros(nt_s.shape,nt_s.dtype.char) if kss.npspins == 2: # spin polarised nv_s = nt_s.copy() nv_s[kss[ij].pspin] += ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vp_s) nv_s = nt_s.copy() nv_s[kss[ij].pspin] -= ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vm_s) else: # spin unpolarised nv = nt_s + ns * kss[ij].get(fg) xc.calculate(gd, nv, vp_s) nv = nt_s - ns * kss[ij].get(fg) xc.calculate(gd, nv, vm_s) vvt_s = (0.5 / ns) * (vp_s - vm_s) timer2.stop() # initialize the correction matrices timer2.start('init v corrections') I_asp = {} for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): # create the modified density matrix Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] P_ii = np.outer(Pi_i, Pj_i) # we need the symmetric form, hence we can pack P_p = pack(P_ii) D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] -= ns * P_p setup = wfs.setups[a] I_sp = np.zeros_like(D_sp) self.xc.calculate_paw_correction(setup, D_sp, I_sp) I_sp *= -1.0 D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] += ns * P_p self.xc.calculate_paw_correction(setup, D_sp, I_sp) I_sp /= 2.0 * ns I_asp[a] = I_sp timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) for kq in range(ij,nij): weight = self.weight_Kijkq(ij, kq) if self.derivativeLevel == 0: # only Exc is available if kss.npspins==2: # spin polarised nv_g = nt_sg.copy() nv_g[kss[ij].pspin] += kss[ij].get(fg) nv_g[kss[kq].pspin] += kss[kq].get(fg) Excpp = xc.get_energy_and_potential( nv_g[0], v_g, nv_g[1], v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] += kss[ij].get(fg) nv_g[kss[kq].pspin] -= kss[kq].get(fg) Excpm = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excmp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -= \ kss[ij].get(fg) nv_g[kss[kq].pspin] -=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) else: # spin unpolarised nv_g=nt_sg + ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excpp = xc.get_energy_and_potential(nv_g,v_g) nv_g=nt_sg + ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excpm = xc.get_energy_and_potential(nv_g,v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excmp = xc.get_energy_and_potential(nv_g,v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excmm = xc.get_energy_and_potential(nv_g,v_g) Om_xc[ij,kq] += weight *\ 0.25*(Excpp-Excmp-Excpm+Excmm)/(ns*ns) elif self.derivativeLevel == 1: # vxc is available timer2.start('integrate') Om_xc[ij,kq] += weight*\ self.gd.integrate(kss[kq].get(fg)* vvt_s[kss[kq].pspin]) timer2.stop() timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() elif self.derivativeLevel == 2: # fxc is available if kss.npspins==2: # spin polarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg) * kss[kq].get(fg) * fxc_sg[kss[ij].pspin, kss[kq].pspin]) else: # spin unpolarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg) * kss[kq].get(fg) * fxc_sg) # XXX still numeric derivatives for local terms timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() if ij != kq: Om_xc[kq,ij] = Om_xc[ij,kq] timer.stop() ## timer2.write() if ij < (nij-1): print >> self.txt,'XC estimated time left',\ self.time_left(timer, t0, ij, nij)
def __init__(self, name, hybrid=None, xc=None, alpha=None, gamma_point=1, method='standard', bandstructure=False, logfilename='-', bands=None, fcut=1e-10, molecule=False, qstride=1, world=None): """Mix standard functionals with exact exchange. name: str Name of functional: EXX, PBE0, HSE03, HSE06 hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. method: str Use 'standard' standard formula and 'acdf for adiabatic-connection dissipation fluctuation formula. alpha: float XXX describe gamma_point: bool 0: Skip k2-k1=0 interactions. 1: Use the alpha method. 2: Integrate the gamma point. bandstructure: bool Calculate bandstructure instead of just the total energy. bands: list of int List of bands to calculate bandstructure for. Default is all bands. molecule: bool Decouple electrostatic interactions between periodically repeated images. fcut: float Threshold for empty band. """ self.alpha = alpha self.fcut = fcut self.gamma_point = gamma_point self.method = method self.bandstructure = bandstructure self.bands = bands self.fd = logfilename self.write_timing_information = True HybridXCBase.__init__(self, name, hybrid, xc) # EXX energies: self.exx = None # total self.evv = None # valence-valence (pseudo part) self.evvacdf = None # valence-valence (pseudo part) self.devv = None # valence-valence (PAW correction) self.evc = None # valence-core self.ecc = None # core-core self.exx_skn = None # bandstructure self.qlatest = None if world is None: world = mpi.world self.world = world self.molecule = molecule if isinstance(qstride, int): qstride = [qstride] * 3 self.qstride_c = np.asarray(qstride) self.timer = Timer()
class HybridXC(HybridXCBase): orbital_dependent = True def __init__(self, name, hybrid=None, xc=None, alpha=None, gamma_point=1, method='standard', bandstructure=False, logfilename='-', bands=None, fcut=1e-10, molecule=False, qstride=1, world=None): """Mix standard functionals with exact exchange. name: str Name of functional: EXX, PBE0, HSE03, HSE06 hybrid: float Fraction of exact exchange. xc: str or XCFunctional object Standard DFT functional with scaled down exchange. method: str Use 'standard' standard formula and 'acdf for adiabatic-connection dissipation fluctuation formula. alpha: float XXX describe gamma_point: bool 0: Skip k2-k1=0 interactions. 1: Use the alpha method. 2: Integrate the gamma point. bandstructure: bool Calculate bandstructure instead of just the total energy. bands: list of int List of bands to calculate bandstructure for. Default is all bands. molecule: bool Decouple electrostatic interactions between periodically repeated images. fcut: float Threshold for empty band. """ self.alpha = alpha self.fcut = fcut self.gamma_point = gamma_point self.method = method self.bandstructure = bandstructure self.bands = bands self.fd = logfilename self.write_timing_information = True HybridXCBase.__init__(self, name, hybrid, xc) # EXX energies: self.exx = None # total self.evv = None # valence-valence (pseudo part) self.evvacdf = None # valence-valence (pseudo part) self.devv = None # valence-valence (PAW correction) self.evc = None # valence-core self.ecc = None # core-core self.exx_skn = None # bandstructure self.qlatest = None if world is None: world = mpi.world self.world = world self.molecule = molecule if isinstance(qstride, int): qstride = [qstride] * 3 self.qstride_c = np.asarray(qstride) self.timer = Timer() def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) self.fd.flush() def calculate_radial(self, rgd, n_sLg, Y_L, v_sg, dndr_sLg=None, rnablaY_Lv=None, tau_sg=None, dedtau_sg=None): return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg, rnablaY_Lv) def calculate_paw_correction(self, setup, D_sp, dEdD_sp=None, addcoredensity=True, a=None): return self.xc.calculate_paw_correction(setup, D_sp, dEdD_sp, addcoredensity, a) def initialize(self, dens, ham, wfs, occupations): assert wfs.bd.comm.size == 1 self.xc.initialize(dens, ham, wfs, occupations) self.dens = dens self.wfs = wfs # Make a k-point descriptor that is not distributed # (self.kd.comm is serial_comm): self.kd = wfs.kd.copy() self.fd = logfile(self.fd, self.world.rank) wfs.initialize_wave_functions_from_restart_file() def set_positions(self, spos_ac): self.spos_ac = spos_ac def calculate(self, gd, n_sg, v_sg=None, e_g=None): # Normal XC contribution: exc = self.xc.calculate(gd, n_sg, v_sg, e_g) # Add EXX contribution: return exc + self.exx * self.hybrid def calculate_exx(self): """Non-selfconsistent calculation.""" self.timer.start('EXX') self.timer.start('Initialization') kd = self.kd wfs = self.wfs if fftw.FFTPlan is fftw.NumpyFFTPlan: self.log('NOT USING FFTW !!') self.log('Spins:', self.wfs.nspins) W = max(1, self.wfs.kd.comm.size // self.wfs.nspins) # Are the k-points distributed? kparallel = (W > 1) # Find number of occupied bands: self.nocc_sk = np.zeros((self.wfs.nspins, kd.nibzkpts), int) for kpt in self.wfs.kpt_u: for n, f in enumerate(kpt.f_n): if abs(f) < self.fcut: self.nocc_sk[kpt.s, kpt.k] = n break else: self.nocc_sk[kpt.s, kpt.k] = self.wfs.bd.nbands self.wfs.kd.comm.sum(self.nocc_sk) noccmin = self.nocc_sk.min() noccmax = self.nocc_sk.max() self.log('Number of occupied bands (min, max): %d, %d' % (noccmin, noccmax)) self.log('Number of valence electrons:', self.wfs.setups.nvalence) if self.bandstructure: self.log('Calculating eigenvalue shifts.') # allocate array for eigenvalue shifts: self.exx_skn = np.zeros((self.wfs.nspins, kd.nibzkpts, self.wfs.bd.nbands)) if self.bands is None: noccmax = self.wfs.bd.nbands else: noccmax = max(max(self.bands) + 1, noccmax) N_c = self.kd.N_c vol = wfs.gd.dv * wfs.gd.N_c.prod() if self.alpha is None: alpha = 6 * vol**(2 / 3.0) / pi**2 else: alpha = self.alpha if self.gamma_point == 1: if alpha == 0.0: qvol = (2*np.pi)**3 / vol / N_c.prod() self.gamma = 4*np.pi * (3*qvol / (4*np.pi))**(1/3.) / qvol else: self.gamma = self.calculate_gamma(vol, alpha) else: kcell_cv = wfs.gd.cell_cv.copy() kcell_cv[0] *= N_c[0] kcell_cv[1] *= N_c[1] kcell_cv[2] *= N_c[2] self.gamma = madelung(kcell_cv) * vol * N_c.prod() / (4 * np.pi) self.log('Value of alpha parameter: %.3f Bohr^2' % alpha) self.log('Value of gamma parameter: %.3f Bohr^2' % self.gamma) # Construct all possible q=k2-k1 vectors: Nq_c = (N_c - 1) // self.qstride_c i_qc = np.indices(Nq_c * 2 + 1, float).transpose( (1, 2, 3, 0)).reshape((-1, 3)) self.bzq_qc = (i_qc - Nq_c) / N_c * self.qstride_c self.q0 = ((Nq_c * 2 + 1).prod() - 1) // 2 # index of q=(0,0,0) assert not self.bzq_qc[self.q0].any() # Count number of pairs for each q-vector: self.npairs_q = np.zeros(len(self.bzq_qc), int) for s in range(kd.nspins): for k1 in range(kd.nibzkpts): for k2 in range(kd.nibzkpts): for K2, q, n1_n, n2 in self.indices(s, k1, k2): self.npairs_q[q] += len(n1_n) self.npairs0 = self.npairs_q.sum() # total number of pairs self.log('Number of pairs:', self.npairs0) # Distribute q-vectors to Q processors: Q = self.world.size // self.wfs.kd.comm.size myrank = self.world.rank // self.wfs.kd.comm.size rank = 0 N = 0 myq = [] nq = 0 for q, n in enumerate(self.npairs_q): if n > 0: nq += 1 if rank == myrank: myq.append(q) N += n if N >= (rank + 1.0) * self.npairs0 / Q: rank += 1 assert len(myq) > 0, 'Too few q-vectors for too many processes!' self.bzq_qc = self.bzq_qc[myq] try: self.q0 = myq.index(self.q0) except ValueError: self.q0 = None self.log('%d x %d x %d k-points' % tuple(self.kd.N_c)) self.log('Distributing %d IBZ k-points over %d process(es).' % (kd.nibzkpts, self.wfs.kd.comm.size)) self.log('Distributing %d q-vectors over %d process(es).' % (nq, Q)) # q-point descriptor for my q-vectors: qd = KPointDescriptor(self.bzq_qc) # Plane-wave descriptor for all wave-functions: self.pd = PWDescriptor(wfs.pd.ecut, wfs.gd, dtype=wfs.pd.dtype, kd=kd) # Plane-wave descriptor pair-densities: self.pd2 = PWDescriptor(self.dens.pd2.ecut, self.dens.gd, dtype=wfs.dtype, kd=qd) self.log('Cutoff energies:') self.log(' Wave functions: %10.3f eV' % (self.pd.ecut * Hartree)) self.log(' Density: %10.3f eV' % (self.pd2.ecut * Hartree)) # Calculate 1/|G+q|^2 with special treatment of |G+q|=0: G2_qG = self.pd2.G2_qG if self.q0 is None: if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] else: self.iG2_qG = [(1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG] else: G2_qG[self.q0][0] = 117.0 # avoid division by zero if self.omega is None: self.iG2_qG = [1.0 / G2_G for G2_G in G2_qG] self.iG2_qG[self.q0][0] = self.gamma else: self.iG2_qG = [(1.0 / G2_G * (1 - np.exp(-G2_G / (4 * self.omega**2)))) for G2_G in G2_qG] self.iG2_qG[self.q0][0] = 1 / (4 * self.omega**2) G2_qG[self.q0][0] = 0.0 # restore correct value # Compensation charges: self.ghat = PWLFC([setup.ghat_l for setup in wfs.setups], self.pd2) self.ghat.set_positions(self.spos_ac) if self.molecule: self.initialize_gaussian() self.log('Value of beta parameter: %.3f 1/Bohr^2' % self.beta) self.timer.stop('Initialization') # Ready ... set ... go: self.t0 = time() self.npairs = 0 self.evv = 0.0 self.evvacdf = 0.0 for s in range(self.wfs.nspins): kpt1_q = [KPoint(self.wfs, noccmax).initialize(kpt) for kpt in self.wfs.kpt_u if kpt.s == s] kpt2_q = kpt1_q[:] if len(kpt1_q) == 0: # No s-spins on this CPU: continue # Send and receive ranks: srank = self.wfs.kd.get_rank_and_index( s, (kpt1_q[0].k - 1) % kd.nibzkpts)[0] rrank = self.wfs.kd.get_rank_and_index( s, (kpt1_q[-1].k + 1) % kd.nibzkpts)[0] # Shift k-points kd.nibzkpts - 1 times: for i in range(kd.nibzkpts): if i < kd.nibzkpts - 1: if kparallel: kpt = kpt2_q[-1].next(self.wfs) kpt.start_receiving(rrank) kpt2_q[0].start_sending(srank) else: kpt = kpt2_q[0] self.timer.start('Calculate') for kpt1, kpt2 in zip(kpt1_q, kpt2_q): # Loop over all k-points that k2 can be mapped to: for K2, q, n1_n, n2 in self.indices(s, kpt1.k, kpt2.k): self.apply(K2, q, kpt1, kpt2, n1_n, n2) self.timer.stop('Calculate') if i < kd.nibzkpts - 1: self.timer.start('Wait') if kparallel: kpt.wait() kpt2_q[0].wait() self.timer.stop('Wait') kpt2_q.pop(0) kpt2_q.append(kpt) self.evv = self.world.sum(self.evv) self.evvacdf = self.world.sum(self.evvacdf) self.calculate_exx_paw_correction() if self.method == 'standard': self.exx = self.evv + self.devv + self.evc + self.ecc elif self.method == 'acdf': self.exx = self.evvacdf + self.devv + self.evc + self.ecc else: 1 / 0 self.log('Exact exchange energy:') for txt, e in [ ('core-core', self.ecc), ('valence-core', self.evc), ('valence-valence (pseudo, acdf)', self.evvacdf), ('valence-valence (pseudo, standard)', self.evv), ('valence-valence (correction)', self.devv), ('total (%s)' % self.method, self.exx)]: self.log(' %-36s %14.6f eV' % (txt + ':', e * Hartree)) self.log('Total time: %10.3f seconds' % (time() - self.t0)) self.npairs = self.world.sum(self.npairs) assert self.npairs == self.npairs0 self.timer.stop('EXX') self.timer.write(self.fd) def calculate_gamma(self, vol, alpha): if self.molecule: return 0.0 N_c = self.kd.N_c offset_c = (N_c + 1) % 2 * 0.5 / N_c bzq_qc = monkhorst_pack(N_c) + offset_c qd = KPointDescriptor(bzq_qc) pd = PWDescriptor(self.wfs.pd.ecut, self.wfs.gd, kd=qd) gamma = (vol / (2 * pi)**2 * sqrt(pi / alpha) * self.kd.nbzkpts) for G2_G in pd.G2_qG: if G2_G[0] < 1e-7: G2_G = G2_G[1:] gamma -= np.dot(np.exp(-alpha * G2_G), G2_G**-1) return gamma / self.qstride_c.prod() def indices(self, s, k1, k2): """Generator for (K2, q, n1, n2) indices for (k1, k2) pair. s: int Spin index. k1: int Index of k-point in the IBZ. k2: int Index of k-point in the IBZ. Returns (K, q, n1_n, n2), where K then index of the k-point in the BZ that k2 is mapped to, q is the index of the q-vector between K and k1, and n1_n is a list of bands that should be combined with band n2.""" for K, k in enumerate(self.kd.bz2ibz_k): if k == k2: for K, q, n1_n, n2 in self._indices(s, k1, k2, K): yield K, q, n1_n, n2 def _indices(self, s, k1, k2, K2): k1_c = self.kd.ibzk_kc[k1] k2_c = self.kd.bzk_kc[K2] q_c = k2_c - k1_c q = abs(self.bzq_qc - q_c).sum(1).argmin() if abs(self.bzq_qc[q] - q_c).sum() > 1e-7: return if self.gamma_point == 0 and q == self.q0: return nocc1 = self.nocc_sk[s, k1] nocc2 = self.nocc_sk[s, k2] # Is k2 in the IBZ? is_ibz2 = (self.kd.ibz2bz_k[k2] == K2) for n2 in range(self.wfs.bd.nbands): # Find range of n1's (from n1a to n1b-1): if is_ibz2: # We get this combination twice, so let's only do half: if k1 >= k2: n1a = n2 else: n1a = n2 + 1 else: n1a = 0 n1b = self.wfs.bd.nbands if self.bandstructure: if n2 >= nocc2: n1b = min(n1b, nocc1) else: if n2 >= nocc2: break n1b = min(n1b, nocc1) if self.bands is not None: assert self.bandstructure n1_n = [] for n1 in range(n1a, n1b): if (n1 in self.bands and n2 < nocc2 or is_ibz2 and n2 in self.bands and n1 < nocc1): n1_n.append(n1) n1_n = np.array(n1_n) else: n1_n = np.arange(n1a, n1b) if len(n1_n) == 0: continue yield K2, q, n1_n, n2 def apply(self, K2, q, kpt1, kpt2, n1_n, n2): k20_c = self.kd.ibzk_kc[kpt2.k] k2_c = self.kd.bzk_kc[K2] if k2_c.any(): self.timer.start('Initialize plane waves') eik2r_R = self.wfs.gd.plane_wave(k2_c) eik20r_R = self.wfs.gd.plane_wave(k20_c) self.timer.stop('Initialize plane waves') else: eik2r_R = 1.0 eik20r_R = 1.0 w1 = self.kd.weight_k[kpt1.k] w2 = self.kd.weight_k[kpt2.k] # Is k2 in the 1. BZ? is_ibz2 = (self.kd.ibz2bz_k[kpt2.k] == K2) e_n = self.calculate_interaction(n1_n, n2, kpt1, kpt2, q, K2, eik20r_R, eik2r_R, is_ibz2) e_n *= 1.0 / self.kd.nbzkpts / self.wfs.nspins * self.qstride_c.prod() if q == self.q0: e_n[n1_n == n2] *= 0.5 f1_n = kpt1.f_n[n1_n] eps1_n = kpt1.eps_n[n1_n] f2 = kpt2.f_n[n2] eps2 = kpt2.eps_n[n2] s_n = np.sign(eps2 - eps1_n) evv = (f1_n * f2 * e_n).sum() evvacdf = 0.5 * (f1_n * (1 - s_n) * e_n + f2 * (1 + s_n) * e_n).sum() self.evv += evv * w1 self.evvacdf += evvacdf * w1 if is_ibz2: self.evv += evv * w2 self.evvacdf += evvacdf * w2 if self.bandstructure: x = self.wfs.nspins self.exx_skn[kpt1.s, kpt1.k, n1_n] += x * f2 * e_n if is_ibz2: self.exx_skn[kpt2.s, kpt2.k, n2] += x * np.dot(f1_n, e_n) def calculate_interaction(self, n1_n, n2, kpt1, kpt2, q, k, eik20r_R, eik2r_R, is_ibz2): """Calculate Coulomb interactions. For all n1 in the n1_n list, calculate interaction with n2.""" # number of plane waves: ng1 = self.wfs.ng_k[kpt1.k] ng2 = self.wfs.ng_k[kpt2.k] # Transform to real space and apply symmetry operation: self.timer.start('IFFT1') if is_ibz2: u2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) else: psit2_R = self.pd.ifft(kpt2.psit_nG[n2, :ng2], kpt2.k) * eik20r_R self.timer.start('Symmetry transform') u2_R = self.kd.transform_wave_function(psit2_R, k) / eik2r_R self.timer.stop() self.timer.stop() # Calculate pair densities: nt_nG = self.pd2.zeros(len(n1_n), q=q) for n1, nt_G in zip(n1_n, nt_nG): self.timer.start('IFFT2') u1_R = self.pd.ifft(kpt1.psit_nG[n1, :ng1], kpt1.k) self.timer.stop() nt_R = u1_R.conj() * u2_R self.timer.start('FFT') nt_G[:] = self.pd2.fft(nt_R, q) self.timer.stop() s = self.kd.sym_k[k] time_reversal = self.kd.time_reversal_k[k] k2_c = self.kd.ibzk_kc[kpt2.k] self.timer.start('Compensation charges') Q_anL = {} # coefficients for shape functions for a, P1_ni in kpt1.P_ani.items(): P1_ni = P1_ni[n1_n] if is_ibz2: P2_i = kpt2.P_ani[a][n2] else: b = self.kd.symmetry.a_sa[s, a] S_c = (np.dot(self.spos_ac[a], self.kd.symmetry.op_scc[s]) - self.spos_ac[b]) assert abs(S_c.round() - S_c).max() < 1e-5 if self.ghat.dtype == complex: x = np.exp(2j * pi * np.dot(k2_c, S_c)) else: x = 1.0 P2_i = np.dot(self.wfs.setups[a].R_sii[s], kpt2.P_ani[b][n2]) * x if time_reversal: P2_i = P2_i.conj() D_np = [] for P1_i in P1_ni: D_ii = np.outer(P1_i.conj(), P2_i) D_np.append(pack(D_ii)) Q_anL[a] = np.dot(D_np, self.wfs.setups[a].Delta_pL) self.timer.start('Expand') if q != self.qlatest: self.f_IG = self.ghat.expand(q) self.qlatest = q self.timer.stop('Expand') # Add compensation charges: self.ghat.add(nt_nG, Q_anL, q, self.f_IG) self.timer.stop('Compensation charges') if self.molecule and n2 in n1_n: nn = (n1_n == n2).nonzero()[0][0] nt_nG[nn] -= self.ngauss_G else: nn = None iG2_G = self.iG2_qG[q] # Calculate energies: e_n = np.empty(len(n1_n)) for n, nt_G in enumerate(nt_nG): e_n[n] = -4 * pi * np.real(self.pd2.integrate(nt_G, nt_G * iG2_G)) self.npairs += 1 if nn is not None: e_n[nn] -= 2 * (self.pd2.integrate(nt_nG[nn], self.vgauss_G) + (self.beta / 2 / pi)**0.5) if self.write_timing_information: t = (time() - self.t0) / len(n1_n) self.log('Time for first pair-density: %10.3f seconds' % t) self.log('Estimated total time: %10.3f seconds' % (t * self.npairs0 / self.world.size)) self.write_timing_information = False return e_n def calculate_exx_paw_correction(self): self.timer.start('PAW correction') self.devv = 0.0 self.evc = 0.0 self.ecc = 0.0 deg = 2 // self.wfs.nspins # spin degeneracy for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.devv -= D_ii[i1, i2] * A / deg self.evc -= np.dot(D_p, setup.X_p) self.ecc += setup.ExxC if not self.bandstructure: self.timer.stop('PAW correction') return Q = self.world.size // self.wfs.kd.comm.size self.exx_skn *= Q for kpt in self.wfs.kpt_u: for a, D_sp in self.dens.D_asp.items(): setup = self.wfs.setups[a] for D_p in D_sp: D_ii = unpack2(D_p) ni = len(D_ii) P_ni = kpt.P_ani[a] for i1 in range(ni): for i2 in range(ni): A = 0.0 for i3 in range(ni): p13 = packed_index(i1, i3, ni) for i4 in range(ni): p24 = packed_index(i2, i4, ni) A += setup.M_pp[p13, p24] * D_ii[i3, i4] self.exx_skn[kpt.s, kpt.k] -= \ (A * P_ni[:, i1].conj() * P_ni[:, i2]).real p12 = packed_index(i1, i2, ni) self.exx_skn[kpt.s, kpt.k] -= \ (P_ni[:, i1].conj() * setup.X_p[p12] * P_ni[:, i2]).real / self.wfs.nspins self.world.sum(self.exx_skn) self.exx_skn *= self.hybrid / Q self.timer.stop('PAW correction') def initialize_gaussian(self): """Calculate gaussian compensation charge and its potential. Used to decouple electrostatic interactions between periodically repeated images for molecular calculations. Charge containing one electron:: (beta/pi)^(3/2)*exp(-beta*r^2), its Fourier transform:: exp(-G^2/(4*beta)), and its potential:: erf(beta^0.5*r)/r. """ gd = self.wfs.gd # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (gd.icell_cv**2).sum(1).max() # Calculate gaussian: G_Gv = self.pd2.G_Qv[self.pd2.Q_qG[0]] G2_G = self.pd2.G2_qG[0] C_v = gd.cell_cv.sum(0) / 2 # center of cell self.ngauss_G = np.exp(-1.0 / (4 * self.beta) * G2_G + 1j * np.dot(G_Gv, C_v)) / gd.dv # Calculate potential from gaussian: R_Rv = gd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) r_R = ((R_Rv - C_v)**2).sum(3)**0.5 if (gd.N_c % 2 == 0).all(): r_R[tuple(gd.N_c // 2)] = 1.0 # avoid dividing by zero v_R = erf(self.beta**0.5 * r_R) / r_R if (gd.N_c % 2 == 0).all(): v_R[tuple(gd.N_c // 2)] = (4 * self.beta / pi)**0.5 self.vgauss_G = self.pd2.fft(v_R) # Compare self-interaction to analytic result: assert abs(0.5 * self.pd2.integrate(self.ngauss_G, self.vgauss_G) - (self.beta / 2 / pi)**0.5) < 1e-6
def get_rpa(self): """calculate RPA part of the omega matrix""" # shorthands kss=self.fullkss finegrid=self.finegrid wfs = self.paw.wfs eh_comm = self.eh_comm # calculate omega matrix nij = len(kss) print >> self.txt,'RPA',nij,'transitions' Om = self.Om for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt,'RPA kss['+'%d'%ij+']=', kss[ij] timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges( finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype.char) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() ## print "shapes 0=",phit.shape,rhot.shape self.restrict(phit_p,phit) else: phit = phit_p rhot = rhot_p for kq in range(ij,nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges( finegrid is 2) timer2.stop() timer2.start('integrate') pre = 2.*sqrt(kss[ij].get_energy()*kss[kq].get_energy()* kss[ij].get_weight()*kss[kq].get_weight()) Om[ij,kq]= pre * self.gd.integrate(rhot*phit) ## print "int=",Om[ij,kq] timer2.stop() # Add atomic corrections timer2.start('integrate corrections 2') Ia = 0. for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] Dij_ii = np.outer(Pi_i, Pj_i) Dij_p = pack(Dij_ii) Pkq_ni = wfs.kpt_u[kss[kq].spin].P_ani[a] Pk_i = Pkq_ni[kss[kq].i] Pq_i = Pkq_ni[kss[kq].j] Dkq_ii = np.outer(Pk_i, Pq_i) Dkq_p = pack(Dkq_ii) C_pp = wfs.setups[a].M_pp # ---- # 2 > P P C P P # ---- ip jr prst ks qt # prst Ia += 2.0*np.dot(Dkq_p,np.dot(C_pp,Dij_p)) timer2.stop() Om[ij,kq] += pre * self.gd.comm.sum(Ia) if ij == kq: Om[ij,kq] += kss[ij].get_energy()**2 else: Om[kq,ij]=Om[ij,kq] timer.stop() ## timer2.write() if ij < (nij-1): t = timer.get_time(ij) # time for nij-ij calculations t = .5*t*(nij-ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'RPA estimated time left',\ self.timestring(t0*(nij-ij-1)+t)
def get_rpa(self): """Calculate RPA and Hartree-fock part of the A+-B matrices.""" # shorthands kss = self.fullkss finegrid = self.finegrid # calculate omega matrix nij = len(kss) print >> self.txt, 'RPAhyb', nij, 'transitions' AmB = np.zeros((nij, nij)) ApB = self.ApB # storage place for Coulomb integrals integrals = {} for ij in range(nij): print >> self.txt,'RPAhyb kss['+'%d'%ij+']=', kss[ij] timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges( finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype) self.poisson.solve(phit_p,rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() self.restrict(phit_p, phit) else: phit = phit_p rhot = rhot_p for kq in range(ij, nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges( finegrid is 2) timer2.stop() pre = self.weight_Kijkq(ij, kq) timer2.start('integrate') I = self.Coulomb_integral_kss(kss[ij], kss[kq], phit, rhot) if kss[ij].spin == kss[kq].spin: name = self.Coulomb_integral_name(kss[ij].i, kss[ij].j, kss[kq].i, kss[kq].j, kss[ij].spin ) integrals[name] = I ApB[ij,kq]= pre * I timer2.stop() if ij == kq: epsij = kss[ij].get_energy() / kss[ij].get_weight() AmB[ij,kq] += epsij ApB[ij,kq] += epsij timer.stop() ## timer2.write() if ij < (nij - 1): t = timer.get_time(ij) # time for nij-ij calculations t = .5*t*(nij-ij) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'RPAhyb estimated time left',\ self.timestring(t0*(nij-ij-1)+t) # add HF parts and apply symmetry timer.start('RPA hyb HF part') if hasattr(self.xc, 'hybrid'): weight = self.xc.hybrid else: weight = 0.0 for ij in range(nij): i = kss[ij].i j = kss[ij].j s = kss[ij].spin for kq in range(ij,nij): if kss[ij].pspin == kss[kq].pspin: k = kss[kq].i q = kss[kq].j ikjq = self.Coulomb_integral_ijkq(i, k, j, q, s, integrals) iqkj = self.Coulomb_integral_ijkq(i, q, k, j, s, integrals) ApB[ij,kq] -= weight * ( ikjq + iqkj ) AmB[ij,kq] -= weight * ( ikjq - iqkj ) ApB[kq,ij] = ApB[ij,kq] AmB[kq,ij] = AmB[ij,kq] timer.stop() return AmB
def get_rpa(self): """calculate RPA part of the omega matrix""" # shorthands kss = self.fullkss finegrid = self.finegrid wfs = self.paw.wfs eh_comm = self.eh_comm # calculate omega matrix nij = len(kss) print >> self.txt, 'RPA', nij, 'transitions' Om = self.Om for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt, 'RPA kss[' + '%d' % ij + ']=', kss[ij] timer = Timer() timer.start('init') timer2 = Timer() # smooth density including compensation charges timer2.start('with_compensation_charges 0') rhot_p = kss[ij].with_compensation_charges(finegrid is not 0) timer2.stop() # integrate with 1/|r_1-r_2| timer2.start('poisson') phit_p = np.zeros(rhot_p.shape, rhot_p.dtype.char) self.poisson.solve(phit_p, rhot_p, charge=None) timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) if finegrid == 1: rhot = kss[ij].with_compensation_charges() phit = self.gd.zeros() ## print "shapes 0=",phit.shape,rhot.shape self.restrict(phit_p, phit) else: phit = phit_p rhot = rhot_p for kq in range(ij, nij): if kq != ij: # smooth density including compensation charges timer2.start('kq with_compensation_charges') rhot = kss[kq].with_compensation_charges(finegrid is 2) timer2.stop() timer2.start('integrate') pre = 2. * sqrt(kss[ij].get_energy() * kss[kq].get_energy() * kss[ij].get_weight() * kss[kq].get_weight()) Om[ij, kq] = pre * self.gd.integrate(rhot * phit) ## print "int=",Om[ij,kq] timer2.stop() # Add atomic corrections timer2.start('integrate corrections 2') Ia = 0. for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] Dij_ii = np.outer(Pi_i, Pj_i) Dij_p = pack(Dij_ii) Pkq_ni = wfs.kpt_u[kss[kq].spin].P_ani[a] Pk_i = Pkq_ni[kss[kq].i] Pq_i = Pkq_ni[kss[kq].j] Dkq_ii = np.outer(Pk_i, Pq_i) Dkq_p = pack(Dkq_ii) C_pp = wfs.setups[a].M_pp # ---- # 2 > P P C P P # ---- ip jr prst ks qt # prst Ia += 2.0 * np.dot(Dkq_p, np.dot(C_pp, Dij_p)) timer2.stop() Om[ij, kq] += pre * self.gd.comm.sum(Ia) if ij == kq: Om[ij, kq] += kss[ij].get_energy()**2 else: Om[kq, ij] = Om[ij, kq] timer.stop() ## timer2.write() if ij < (nij - 1): t = timer.get_time(ij) # time for nij-ij calculations t = .5 * t * (nij - ij ) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'RPA estimated time left',\ self.timestring(t0*(nij-ij-1)+t)
def get_xc(self): """Add xc part of the coupling matrix""" # shorthands paw = self.paw wfs = paw.wfs gd = paw.density.finegd comm = gd.comm eh_comm = self.eh_comm fg = self.finegrid is 2 kss = self.fullkss nij = len(kss) Om_xc = self.Om # initialize densities # nt_sg is the smooth density on the fine grid with spin index if kss.nvspins == 2: # spin polarised ground state calc. nt_sg = paw.density.nt_sg else: # spin unpolarised ground state calc. if kss.npspins == 2: # construct spin polarised densities nt_sg = np.array( [.5 * paw.density.nt_sg[0], .5 * paw.density.nt_sg[0]]) else: nt_sg = paw.density.nt_sg # check if D_sp have been changed before D_asp = self.paw.density.D_asp for a, D_sp in D_asp.items(): if len(D_sp) != kss.npspins: if len(D_sp) == 1: D_asp[a] = np.array([0.5 * D_sp[0], 0.5 * D_sp[0]]) else: D_asp[a] = np.array([D_sp[0] + D_sp[1]]) # restrict the density if needed if fg: nt_s = nt_sg else: nt_s = self.gd.zeros(nt_sg.shape[0]) for s in range(nt_sg.shape[0]): self.restrict(nt_sg[s], nt_s[s]) gd = paw.density.gd # initialize vxc or fxc if self.derivativeLevel == 0: raise NotImplementedError if kss.npspins == 2: v_g = nt_sg[0].copy() else: v_g = nt_sg.copy() elif self.derivativeLevel == 1: pass elif self.derivativeLevel == 2: raise NotImplementedError if kss.npspins == 2: fxc = d2Excdnsdnt(nt_sg[0], nt_sg[1]) else: fxc = d2Excdn2(nt_sg) else: raise ValueError('derivativeLevel can only be 0,1,2') ## self.paw.my_nuclei = [] ns = self.numscale xc = self.xc print >> self.txt, 'XC', nij, 'transitions' for ij in range(eh_comm.rank, nij, eh_comm.size): print >> self.txt, 'XC kss[' + '%d' % ij + ']' timer = Timer() timer.start('init') timer2 = Timer() if self.derivativeLevel == 1: # vxc is available # We use the numerical two point formula for calculating # the integral over fxc*n_ij. The results are # vvt_s smooth integral # nucleus.I_sp atom based correction matrices (pack2) # stored on each nucleus timer2.start('init v grids') vp_s = np.zeros(nt_s.shape, nt_s.dtype.char) vm_s = np.zeros(nt_s.shape, nt_s.dtype.char) if kss.npspins == 2: # spin polarised nv_s = nt_s.copy() nv_s[kss[ij].pspin] += ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vp_s) nv_s = nt_s.copy() nv_s[kss[ij].pspin] -= ns * kss[ij].get(fg) xc.calculate(gd, nv_s, vm_s) else: # spin unpolarised nv = nt_s + ns * kss[ij].get(fg) xc.calculate(gd, nv, vp_s) nv = nt_s - ns * kss[ij].get(fg) xc.calculate(gd, nv, vm_s) vvt_s = (0.5 / ns) * (vp_s - vm_s) timer2.stop() # initialize the correction matrices timer2.start('init v corrections') I_asp = {} for a, P_ni in wfs.kpt_u[kss[ij].spin].P_ani.items(): # create the modified density matrix Pi_i = P_ni[kss[ij].i] Pj_i = P_ni[kss[ij].j] P_ii = np.outer(Pi_i, Pj_i) # we need the symmetric form, hence we can pack P_p = pack(P_ii) D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] -= ns * P_p setup = wfs.setups[a] I_sp = np.zeros_like(D_sp) setup.xc_correction.calculate(self.xc, D_sp, I_sp) I_sp *= -1.0 D_sp = self.paw.density.D_asp[a].copy() D_sp[kss[ij].pspin] += ns * P_p setup.xc_correction.calculate(self.xc, D_sp, I_sp) I_sp /= 2.0 * ns I_asp[a] = I_sp timer2.stop() timer.stop() t0 = timer.get_time('init') timer.start(ij) for kq in range(ij, nij): weight = self.weight_Kijkq(ij, kq) if self.derivativeLevel == 0: # only Exc is available if kss.npspins == 2: # spin polarised nv_g = nt_sg.copy() nv_g[kss[ij].pspin] +=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] +=\ kss[ij].get(fg) nv_g[kss[kq].pspin] -= \ kss[kq].get(fg) Excpm = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -=\ kss[ij].get(fg) nv_g[kss[kq].pspin] +=\ kss[kq].get(fg) Excmp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) nv_g = nt_sg.copy() nv_g[kss[ij].pspin] -= \ kss[ij].get(fg) nv_g[kss[kq].pspin] -=\ kss[kq].get(fg) Excpp = xc.get_energy_and_potential(\ nv_g[0],v_g,nv_g[1],v_g) else: # spin unpolarised nv_g=nt_sg + ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excpp = xc.get_energy_and_potential(nv_g, v_g) nv_g=nt_sg + ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excpm = xc.get_energy_and_potential(nv_g, v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ + ns*kss[kq].get(fg) Excmp = xc.get_energy_and_potential(nv_g, v_g) nv_g=nt_sg - ns*kss[ij].get(fg)\ - ns*kss[kq].get(fg) Excmm = xc.get_energy_and_potential(nv_g, v_g) Om_xc[ij,kq] += weight *\ 0.25*(Excpp-Excmp-Excpm+Excmm)/(ns*ns) elif self.derivativeLevel == 1: # vxc is available timer2.start('integrate') Om_xc[ij,kq] += weight*\ self.gd.integrate(kss[kq].get(fg)* vvt_s[kss[kq].pspin]) timer2.stop() timer2.start('integrate corrections') Exc = 0. for a, P_ni in wfs.kpt_u[kss[kq].spin].P_ani.items(): # create the modified density matrix Pk_i = P_ni[kss[kq].i] Pq_i = P_ni[kss[kq].j] P_ii = np.outer(Pk_i, Pq_i) # we need the symmetric form, hence we can pack # use pack as I_sp used pack2 P_p = pack(P_ii) Exc += np.dot(I_asp[a][kss[kq].pspin], P_p) Om_xc[ij, kq] += weight * self.gd.comm.sum(Exc) timer2.stop() elif self.derivativeLevel == 2: # fxc is available if kss.npspins == 2: # spin polarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg)* kss[kq].get(fg)* fxc[kss[ij].pspin,kss[kq].pspin]) else: # spin unpolarised Om_xc[ij,kq] += weight *\ gd.integrate(kss[ij].get(fg)* kss[kq].get(fg)* fxc) if ij != kq: Om_xc[kq, ij] = Om_xc[ij, kq] timer.stop() ## timer2.write() if ij < (nij - 1): t = timer.get_time(ij) # time for nij-ij calculations t = .5 * t * (nij - ij ) # estimated time for n*(n+1)/2, n=nij-(ij+1) print >> self.txt,'XC estimated time left',\ self.timestring(t0*(nij-ij-1)+t)
class DipoleQuad: """ Class to handle external potential due to dipole and quadrupole from SCME type calculator object. """ def __init__(self, mm, qm, mp, calcmm, dyn=False, g=0.5): self.mm = mm # mm atoms object self.mp = mp # no. atoms per center of mass (cm) self.dyn = dyn # dyn. update of potential? self.calcmm = calcmm # mm calculator (SCME) self.qm = qm # qm atoms object # self.qmidx = len(qm)# no. QM atoms # self.nm = len(self.mm) / self.mp self.cm = self.get_cm(self.nm) self.timer = Timer() # smoothing value self.g = g # initialization self.initial = True # Hold on to old arrays self.dipoles = None self.qpoles = None self.dipoles_1 = None self.qpoles_1 = None def get_potential(self, gd=None, density=None, setups=None, nspins=None): """ Create external potential from dipoles and quadrupoles with origin at the center of mass of each classical molecule in the atoms object """ if self.initial: self.update_potential(gd=gd, density=density, setups=setups, nspins=nspins) return self.potential elif self.dyn: if not self.check_convergence(): self.update_potential(gd=gd, density=density, setups=setups, nspins=nspins) return self.potential else: if hasattr(self, 'potential'): if gd == self.gd or gd is None: # Nothing changed return self.potential def update_potential(self, gd=None, density=None, setups=None, nspins=None): # Save old dipoles if self.dipoles is not None: self.dipoles_1 = self.dipoles.copy() self.qpoles_1 = self.qpoles.copy() self.gd = gd # Grab electric field and derivative values self.timer.start('Electric Field and Derivative') eF, deF = self.get_efield(density, setups, nspins) self.timer.stop('Electric Field and Derivative') calcmm = self.calcmm mm = self.mm ####################################### # Pass (new) values to SCME calcmm.eF = eF # <-- ! CHECK calcmm.deF = deF self.timer.start('SCME Calculation') calcmm.calculate(mm) self.timer.stop('SCME Calculation') ####################################### # Values are in atomic-units dipole = calcmm.dipoles / Bohr qpoles = calcmm.qpoles / Bohr**2 # if rank == MASTER: print 'dipoles' print dipole * Bohr / Debye print 'qpoles' print qpoles * Bohr**2 / Debye # No. solvent mols n = len(self.mm) / self.mp # Make empty POT potential = np.zeros(gd.end_c-gd.beg_c) sG = (np.indices(gd.n_c, float).T + \ gd.beg_c) / gd.N_c # Dipole is something with Bohr**2 # Place external potential due to Dipoles and Quads on grid # For a given MM mol, get all distances for a in range(n): nsG = sG - np.linalg.solve(gd.cell_cv.T, self.cm[a]) # drX, drY, drZ distances: xyz = np.dot(nsG, gd.cell_cv) # mUr mUr = np.dot(xyz,dipole[a]) # Q = qpoles[a,:,:] # |r - rcm| dis = np.sqrt(((xyz.T)**2).sum(axis=0)) dis_d = smooth(dis, self.g) dis_q = smooth(dis, 0.3) # Add dipole component potential += mUr.T / dis_d**3 # Quadrupole components: for i in range(3): for j in range(3): potential += Q[i,j]*xyz.T[i,:,:,:]*xyz.T[j,:,:,:] / dis_q**5 if i == j: potential -= 1./3 * Q[i,j] / dis_q**3 # Potential updated self.initial = False # Hold on to self.gd = gd self.potential = potential self.eF = eF self.deF = deF self.qpoles = qpoles.copy() self.dipoles = dipole.copy() def check_convergence(self): if self.dipoles_1 is not None: dip = abs(self.dipoles - self.dipoles_1).sum() qua = abs(self.qpoles - self.qpoles_1).sum() if rank == MASTER: print dip, qua return np.max([dip]) < 1e-5 else: return False def get_efield(self, density, setups, nspins): """ Evaluate electric field at each cm from total psuedo charge density. """ gd = self.gd eF = np.zeros((3,self.nm)) deF = np.zeros((3,3,self.nm)) # Grab comp. charges comp = np.zeros(self.qmidx) density.Q_aL = {} for a, D_sp in density.D_asp.items(): Q_L = density.Q_aL[a] = np.dot(D_sp[:nspins].sum(0), setups[a].Delta_pL) Q_L[0] += setups[a].Delta0 comp[a] += Q_L[0] # Collect over gd domains #wfs.gd.comm.sum(comp) comp *= -1*sqrt(4.*pi) # Grab pseudo-density on gd if density.nt_sg is None: density.interpolate_pseudo_density() nt_sG = density.nt_sg # if density.nspins == 1: nt_g = nt_sG[0] else: nt_g = nt_sG.sum(axis=0) #assert np.shape(nt_g) == np.shape(self.gd) # sG = (np.indices(gd.n_c, float).T + \ gd.beg_c) / gd.N_c # Arrays # for a, pos in enumerate(self.cm): # Get all gpt distances relative to molecule a nsG = sG - np.linalg.solve(gd.cell_cv.T, self.cm[a]) # r(xyz) to all gpts xyz = np.dot(nsG, gd.cell_cv) # distance to all gpts dis = np.sqrt(((xyz.T)**2).sum(axis=0)) # dis = smooth(dis, self.g) # total field on cm due to density eFT = (xyz.T)*nt_g*gd.dv / dis**3 eF[:,a] += [eFT[0].sum(),eFT[1].sum(),eFT[2].sum()] # nuclei-to-dipole xyz_n = self.qm.get_positions() / Bohr - pos dis_n = np.sqrt((xyz_n**2).sum(axis=1)) eF[:,a] -= (xyz_n.T * comp / dis_n**3).T.sum(axis=0) # Loop for deF for n in range(3): # ... nr_ir = xyz.T[n]*xyz.T*nt_g*3.*gd.dv / dis**5 deF[n,:,a] -= [nr_ir[0].sum(),nr_ir[1].sum(),nr_ir[2].sum()] deF[n,n,a] += (nt_g*gd.dv / dis**3).sum() # comp deF[n,:,a] += 3*(xyz_n.T[n]*xyz_n.T*comp / dis_n**5).T.sum(axis=0) deF[n,n,a] -= (comp / dis_n**3).sum() gd.comm.sum(eF) gd.comm.sum(deF) # Change units to D/A, D/AA return eF/Bohr**2, deF/Bohr**3 def get_nuclear_energy(self, nucleus): return -1. * nucleus.setup.Z * self.get_value(spos_c = nucleus.spos_c) def get_value(self, position=None, spos_c=None): """ Potential value as seen by an electron at a certain gridpoint """ if position is None: vr = spos_c * self.gd.h_cv * self.gd.N_c else: vr = position dipole = self.dipole quad = self.quad n = len(self.atoms / self.mp) self.get_cm(n,self.mp) v = 0 # Eval. external potential at vr for a in range(n): dr = vr - self.cm[a] dis = np.sqrt((dr**2).sum()) # mUr mUr = np.dot(dis,dipole[a]) Q = quad[:,:,a] v += mUr / dis**3 return v def get_taylor(self, position=None, spos_c=None): return [[0]] def get_cm(self, n): cm = np.zeros((n,3)) atoms = self.mm mp = self.mp for i in range(n): cm[i,:] += atoms[i*mp:(i+1)*mp].get_center_of_mass() / Bohr return cm