def bilinear_concentric_potential(r_g, dr_g, f_g, ft_g, l1, l2, alpha, rfilt=None): """Calculate corrections for concentric functions and potentials:: / _ _a _ _a ~ _ _a ~ _ _a _ v = | f (r - R ) V(r - R ) - f (r - R ) V(r - R ) dr m1,m2 / L1,L2 L1,L2 where f(r) and ft(r) are bilinear product of two localized functions which are radial splines times real spherical harmonics (l1,m1) or (l2,m2) and:: _ 1 _ -1 ~ _ erf(alpha*r) _ -1 V(r) = --------- |r| ^ V(r) = ------------ |r| 4 pi eps0 4 pi eps0 Note that alpha (and rfilt) should conform with the cutoff radius. """ work_g = erf(alpha*r_g) if rfilt is None: M = np.vdot(f_g - ft_g * work_g, r_g * dr_g) else: M = np.vdot((f_g - ft_g * work_g)[r_g>=rfilt], \ (r_g * dr_g)[r_g>=rfilt]) # Replace 1/r -> (3-r^2/rfilt^2)/(2*rfilt) for r < rfilt M += np.vdot((f_g - ft_g * work_g)[r_g<rfilt], \ (r_g**2/(2*rfilt) * (3-(r_g/rfilt)**2) * dr_g)[r_g<rfilt]) v_mm = np.empty((2*l1+1, 2*l2+1), dtype=float) for m1 in range(2*l1+1): for m2 in range(2*l2+1): v_mm[m1,m2] = M * intYY(l1, m1-l1, l2, m2-l2) return v_mm
def __init__(self, cell_cv, nk_c, txt=sys.stdout): self.nk_c = nk_c bigcell_cv = cell_cv * nk_c[:, np.newaxis] L_c = (np.linalg.inv(bigcell_cv)**2).sum(0)**-0.5 rc = 0.5 * L_c.min() prnt('Inner radius for %dx%dx%d Wigner-Seitz cell: %.3f Ang' % (tuple(nk_c) + (rc * Bohr,)), file=txt) self.a = 5 / rc prnt('Range-separation parameter: %.3f Ang^-1' % (self.a / Bohr), file=txt) # nr_c = [get_efficient_fft_size(2 * int(L * self.a * 1.5)) nr_c = [get_efficient_fft_size(2 * int(L * self.a * 3.0)) for L in L_c] prnt('FFT size for calculating truncated Coulomb: %dx%dx%d' % tuple(nr_c), file=txt) self.gd = GridDescriptor(nr_c, bigcell_cv, comm=mpi.serial_comm) v_R = self.gd.empty() v_i = v_R.ravel() pos_iv = self.gd.get_grid_point_coordinates().reshape((3, -1)).T corner_jv = np.dot(np.indices((2, 2, 2)).reshape((3, 8)).T, bigcell_cv) for i, pos_v in enumerate(pos_iv): r = ((pos_v - corner_jv)**2).sum(axis=1).min()**0.5 if r == 0: v_i[i] = 2 * self.a / pi**0.5 else: v_i[i] = erf(self.a * r) / r self.K_Q = np.fft.fftn(v_R) * self.gd.dv
def __init__(self, cell_cv, nk_c, txt=sys.stdout): self.nk_c = nk_c bigcell_cv = cell_cv * nk_c[:, np.newaxis] L_c = (np.linalg.inv(bigcell_cv)**2).sum(0)**-0.5 rc = 0.5 * L_c.min() print('Inner radius for %dx%dx%d Wigner-Seitz cell: %.3f Ang' % (tuple(nk_c) + (rc * Bohr, )), file=txt) self.a = 5 / rc print('Range-separation parameter: %.3f Ang^-1' % (self.a / Bohr), file=txt) # nr_c = [get_efficient_fft_size(2 * int(L * self.a * 1.5)) nr_c = [get_efficient_fft_size(2 * int(L * self.a * 3.0)) for L in L_c] print('FFT size for calculating truncated Coulomb: %dx%dx%d' % tuple(nr_c), file=txt) self.gd = GridDescriptor(nr_c, bigcell_cv, comm=mpi.serial_comm) v_R = self.gd.empty() v_i = v_R.ravel() pos_iv = self.gd.get_grid_point_coordinates().reshape((3, -1)).T corner_jv = np.dot(np.indices((2, 2, 2)).reshape((3, 8)).T, bigcell_cv) for i, pos_v in enumerate(pos_iv): r = ((pos_v - corner_jv)**2).sum(axis=1).min()**0.5 if r == 0: v_i[i] = 2 * self.a / pi**0.5 else: v_i[i] = erf(self.a * r) / r self.K_Q = np.fft.fftn(v_R) * self.gd.dv
def __init__(self, gd, a=19., center=None): self.gd = gd self.xyz, self.r2 = coordinates(gd, center) self.r = np.sqrt(self.r2) self.set_width(a) self.exp_ar2 = exp(-self.a * self.r2) self.erf_sar = erf(sqrt(self.a) * self.r)
def dipole_correction(c, gd, rhot_g): """Get dipole corrections to charge and potential. Returns arrays drhot_g and dphit_g such that if rhot_g has the potential phit_g, then rhot_g + drhot_g has the potential phit_g + dphit_g, where dphit_g is an error function. The error function is chosen so as to be largely constant at the cell boundaries and beyond. """ # This implementation is not particularly economical memory-wise moment = gd.calculate_dipole_moment(rhot_g)[c] if abs(moment) < 1e-12: return gd.zeros(), gd.zeros(), 0.0 r_g = gd.get_grid_point_coordinates()[c] cellsize = abs(gd.cell_cv[c, c]) sr_g = 2.0 / cellsize * r_g - 1.0 # sr ~ 'scaled r' alpha = 12.0 # should perhaps be variable drho_g = sr_g * np.exp(-alpha * sr_g**2) moment2 = gd.calculate_dipole_moment(drho_g)[c] factor = -moment / moment2 drho_g *= factor phifactor = factor * (np.pi / alpha)**1.5 * cellsize**2 / 4.0 dphi_g = -phifactor * erf(sr_g * np.sqrt(alpha)) return drho_g, dphi_g, phifactor
def get_dipole_correction(self, gd, rhot_g): """Get dipole corrections to charge and potential. Returns arrays drhot_g and dphit_g such that if rhot_g has the potential phit_g, then rhot_g + drhot_g has the potential phit_g + dphit_g, where dphit_g is an error function. The error function is chosen so as to be largely constant at the cell boundaries and beyond. """ # This implementation is not particularly economical memory-wise if not gd.orthogonal: raise ValueError('Dipole correction requires orthorhombic cell') c = self.c moment = gd.calculate_dipole_moment(rhot_g)[c] if abs(moment) < 1e-12: return gd.zeros(), gd.zeros() r_g = gd.get_grid_point_coordinates()[c] cellsize = abs(gd.cell_cv[c, c]) sr_g = 2.0 / cellsize * r_g - 1.0 # sr ~ 'scaled r' alpha = 12.0 # should perhaps be variable drho_g = sr_g * np.exp(-alpha * sr_g**2) moment2 = gd.calculate_dipole_moment(drho_g)[c] factor = -moment / moment2 drho_g *= factor phifactor = factor * (np.pi / alpha)**1.5 * cellsize**2 / 4.0 dphi_g = -phifactor * erf(sr_g * np.sqrt(alpha)) return drho_g, dphi_g
def screen_potential(r, v, charge): """Split long-range potential into short-ranged contributions. The potential v is a long-ranted potential with the asymptotic form Z/r corresponding to the given charge. Return a potential vscreened and charge distribution rhocomp such that v(r) = vscreened(r) + vHartree[rhocomp](r). The returned quantities are truncated to a reasonable cutoff radius. """ vr = v * r + charge err = 0.0 i = len(vr) while err < 1e-5: # Things can be a bit sensitive to the threshold. The O.pz-mt setup # gets 20-30 Bohr long compensation charges if it's 1e-6. i -= 1 err = abs(vr[i]) i += 1 icut = np.searchsorted(r, r[i] * 1.1) rcut = r[icut] rshort = r[:icut] a = rcut / 5.0 # XXX why is this so important? vcomp = charge * erf(rshort / (np.sqrt(2.0) * a)) / rshort # XXX divide by r rhocomp = charge * (np.sqrt(2.0 * np.pi) * a)**(-3) * \ np.exp(-0.5 * (rshort / a)**2) vscreened = v[:icut] + vcomp return vscreened, rhocomp
def __init__(self, alpha1=10.0, alpha2=300.0): self.alpha1 = alpha1 self.alpha2 = alpha2 self.natoms = 0 self.E = 0.0 self.Z = 1 self.Nc = 0 self.Nv = 1 self.nao = None self.pt_j = [] self.ni = 0 self.l_j = [] self.n_j = [] self.nct = Spline(0, 0.5, [0.0, 0.0, 0.0]) self.Nct = 0.0 self.N0_p = [] rc = 2.0 r_g = np.linspace(0, rc, 100) r2_g = r_g**2 self.ghat_l = [ Spline(0, rc, 4 * alpha1**1.5 / np.pi**0.5 * np.exp(-alpha1 * r2_g)) ] v_g = erf(alpha1**0.5 * r_g) - erf(alpha2**0.5 * r_g) v_g[1:] *= (4 * np.pi)**0.5 / r_g[1:] v_g[0] = 4 * (alpha1**0.5 - alpha2**0.5) self.vbar = Spline(0, rc, v_g) self.Delta_pL = np.zeros((0, 1)) self.Delta0 = -1 / (4 * np.pi)**0.5 self.lmax = 0 self.K_p = self.M_p = self.MB_p = np.zeros(0) self.M_pp = np.zeros((0, 0)) self.Kc = 0.0 self.MB = 0.0 self.M = -(alpha1 / 2 / np.pi)**0.5 self.xc_correction = None self.HubU = None self.dO_ii = np.zeros((0, 0)) self.type = 'all-electron' self.fingerprint = None self.symbol = 'H'
def get_electrostatic_potential(self, r, r_B, q_B, excludefroml0=None): """... Calculates the electrostatic potential at point r_i from point charges at {r_B} in a lattice using the Ewald summation. Charge neutrality is obtained by adding the homogenous density q_hom/V:: ---- ----' - \ \ q_j q_hom / 1 phi(r_i) = | | --------------- + ----- |dr --------- / / |r_i - r_j + l| V | |r - r_i| ---- ---- / j l r_B : matrix with the lattice basis (in cartesian coordinates). q_B : probe charges (in units of e). excludefroml0 : integer specifying if a point charge is not to be included in the central (l=0) unit cell. Used for Madelung constants. """ E0 = 0. if excludefroml0 is None: excludefroml0 = np.zeros(len(q_B), dtype=int) if excludefroml0 in range(len(q_B)): i = excludefroml0 excludefroml0 = np.zeros(len(q_B), dtype=int) excludefroml0[i] = 1 assert sum(excludefroml0) <= 1 for i, q in enumerate(q_B): # potential from point charges rprime = r - r_B[i] absr = np.linalg.norm(rprime) E0 += q * self.get_sum_real_ij(rprime) E0 += q * self.get_sum_recip_ij(rprime) if excludefroml0[i]: # if sum over l not 0 if absr < 1e-14: # lim r -> 0 erf(r G) / r = 2 * G / sqrt(pi) E0 -= q * 2. * self.G / np.sqrt(pi) else: E0 -= q * erf(absr * self.G) / absr else: # if sum over all l E0 += q * erfc(absr * self.G) / absr # correct for compensating homogeneous background q_hom = -sum(q_B) E0 += q_hom * pi / (self.G**2 * self.Vcell) return E0
def bilinear_concentric_force(r_g, dr_g, f_g, ft_g, l1, l2, alpha, rfilt=None): """Calculate corrections for concentric functions and potentials:: _ / _ _a __ _ _a ~ _ _a __ ~ _ _a _ F = | f (r - R ) \/_ V(r - R ) - f (r - R ) \/_ V(r - R ) dr m1,m2 / L1,L2 r L1,L2 r where f(r) and ft(r) are bilinear product of two localized functions which are radial splines times real spherical harmonics (l1,m1) or (l2,m2) and:: _ 1 _ -1 ~ _ erf(alpha*r) _ -1 V(r) = --------- |r| ^ V(r) = ------------ |r| 4 pi eps0 4 pi eps0 Note that alpha (and rfilt) should conform with the cutoff radius. """ work_g = erf(alpha*r_g) - 2*alpha/np.pi**0.5 \ * r_g * np.exp(-alpha**2 * r_g**2) if rfilt is None: M = - np.vdot(f_g - ft_g * work_g, dr_g) else: M = - np.vdot((f_g - ft_g * work_g)[r_g>=rfilt], dr_g[r_g>=rfilt]) # Replace 1/r -> (3-r^2/rfilt^2)/(2*rfilt) for r < rfilt work_g = (r_g/rfilt) * erf(alpha*r_g) - alpha*rfilt/np.pi**0.5 \ * (3-(r_g/rfilt)**2) * np.exp(-alpha**2 * r_g**2) M += - np.vdot((f_g * (r_g/rfilt) - ft_g * work_g)[r_g<rfilt], \ ((r_g/rfilt)**2 * dr_g)[r_g<rfilt]) F_mmv = np.empty((2*l1+1, 2*l2+1, 3), dtype=float) for m1 in range(2*l1+1): for m2 in range(2*l2+1): F_mmv[m1,m2,0] = M * intYY_ex(l1, m1-l1, l2, m2-l2) F_mmv[m1,m2,1] = M * intYY_ey(l1, m1-l1, l2, m2-l2) F_mmv[m1,m2,2] = M * intYY_ez(l1, m1-l1, l2, m2-l2) return F_mmv
def I1(R, ap1, b, alpha, beta, m=0): if ap1 == (0, 0, 0): if b != (0, 0, 0): return I1(-R, b, ap1, beta, alpha, m) else: f = 2 * np.sqrt(np.pi**5 / (alpha + beta)) / (alpha * beta) if np.sometrue(R): T = alpha * beta / (alpha + beta) * np.dot(R, R) f1 = f * erf(T**0.5) * (np.pi / T)**0.5 if m == 0: return 0.5 * f1 f2 = f * np.exp(-T) / T**m if m == 1: return 0.25 * f1 / T - 0.5 * f2 if m == 2: return 0.375 * f1 / T**2 - 0.5 * f2 * (1.5 + T) if m == 3: return 0.9375 * f1 / T**3 - 0.25 * f2 * (7.5 + T * (5 + 2 * T)) if m == 4: return 3.28125 * f1 / T**4 - 0.125 * f2 * \ (52.5 + T * (35 + 2 * T * (7 + 2 * T))) if m == 5: return 14.7656 * f1 / T**5 - 0.03125 * f2 * \ (945 + T * (630 + T * (252 + T * (72 + T * 16)))) if m == 6: return 81.2109 * f1 / T**6 - 0.015625 * f2 * \ (10395 + T * (6930 + T * (2772 + T * (792 + T * (176 + T * 32))))) else: raise NotImplementedError return f / (1 + 2 * m) for i in range(3): if ap1[i] > 0: break a = ap1[:i] + (ap1[i] - 1,) + ap1[i + 1:] result = beta / (alpha + beta) * R[i] * I1(R, a, b, alpha, beta, m + 1) if a[i] > 0: am1 = a[:i] + (a[i] - 1,) + a[i + 1:] result += a[i] / (2 * alpha) * (I1(R, am1, b, alpha, beta, m) - beta / (alpha + beta) * I1(R, am1, b, alpha, beta, m + 1)) if b[i] > 0: bm1 = b[:i] + (b[i] - 1,) + b[i + 1:] result += b[i] / (2 * (alpha + beta)) * I1(R, a, bm1, alpha, beta, m + 1) return result
def I1(R, ap1, b, alpha, beta, m=0): if ap1 == (0, 0, 0): if b != (0, 0, 0): return I1(-R, b, ap1, beta, alpha, m) else: f = 2 * np.sqrt(np.pi**5 / (alpha + beta)) / (alpha * beta) if np.sometrue(R): T = alpha * beta / (alpha + beta) * np.dot(R, R) f1 = f * erf(T**0.5) * (np.pi / T)**0.5 if m == 0: return 0.5 * f1 f2 = f * np.exp(-T) / T**m if m == 1: return 0.25 * f1 / T - 0.5 * f2 if m == 2: return 0.375 * f1 / T**2 - 0.5 * f2 * (1.5 + T) if m == 3: return 0.9375 * f1 / T**3 - 0.25 * f2 * (7.5 + T * (5 + 2 * T)) if m == 4: return 3.28125 * f1 / T**4 - 0.125 * f2 * \ (52.5 + T * (35 + 2 * T * (7 + 2 * T))) if m == 5: return 14.7656 * f1 / T**5 - 0.03125 * f2 * \ (945 + T * (630 + T * (252 + T * (72 + T * 16)))) if m == 6: return 81.2109 * f1 / T**6 - 0.015625 * f2 * \ (10395 + T * (6930 + T * (2772 + T * (792 + T * (176 + T * 32))))) else: raise NotImplementedError return f / (1 + 2 * m) for i in range(3): if ap1[i] > 0: break a = ap1[:i] + (ap1[i] - 1, ) + ap1[i + 1:] result = beta / (alpha + beta) * R[i] * I1(R, a, b, alpha, beta, m + 1) if a[i] > 0: am1 = a[:i] + (a[i] - 1, ) + a[i + 1:] result += a[i] / ( 2 * alpha) * (I1(R, am1, b, alpha, beta, m) - beta / (alpha + beta) * I1(R, am1, b, alpha, beta, m + 1)) if b[i] > 0: bm1 = b[:i] + (b[i] - 1, ) + b[i + 1:] result += b[i] / (2 * (alpha + beta)) * I1(R, a, bm1, alpha, beta, m + 1) return result
def f(self, f): if self.width is None: if f > self.eps: return sqrt(f) else: return 0.0 else: dEH = -f w = self.width if dEH / w < -100: return sqrt(f) Knew = -0.5 * erf(sqrt((max(0.0,dEH)-dEH)/w)) * \ sqrt(w*pi) * exp(-dEH/w) Knew += 0.5 * sqrt(w * pi) * exp(-dEH / w) Knew += sqrt(max(0.0, dEH) - dEH) * exp(max(0.0, dEH) / w) #print dEH, w, dEH/w, Knew, f**0.5 return Knew
def f(self, f): if self.width is None: if f > self.eps: return sqrt(f) else: return 0.0 else: dEH = -f w = self.width if dEH / w < -100: return sqrt(f) Knew = -0.5 * erf(sqrt((max(0.0,dEH)-dEH)/w)) * \ sqrt(w*pi) * exp(-dEH/w) Knew += 0.5 * sqrt(w*pi)*exp(-dEH/w) Knew += sqrt(max(0.0,dEH)-dEH)*exp(max(0.0,dEH)/w) #print dEH, w, dEH/w, Knew, f**0.5 return Knew
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.get_reciprocal_vectors() 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 distribution(self, kpt, fermilevel): x = (kpt.eps_n - fermilevel) / self.width x = x.clip(-100, 100) z = 0.5 * (1 - erf(x)) for i in range(self.iter): z += self.coff_function(i + 1) * self.hermite_poly(2 * i + 1, x) * np.exp(-x**2) kpt.f_n[:] = kpt.weight * z n = kpt.f_n.sum() dnde = 1. / np.sqrt(pi) * np.exp(-x**2) for i in range(self.iter): dnde += self.coff_function(i + 1) * self.hermite_poly(2 * i + 2, x) * np.exp(-x**2) dnde = dnde.sum() dnde /= self.width e_entropy = 0.5 * self.coff_function(self.iter) * self.hermite_poly(2 * self.iter, x)* np.exp(-x**2) e_entropy = kpt.weight * e_entropy.sum() * self.width sign = 1 - kpt.s * 2 return np.array([n, dnde, n * sign, e_entropy])
def distribution(self, kpt, fermilevel): x = (kpt.eps_n - fermilevel) / self.width x = x.clip(-100, 100) z = 0.5 * (1 - erf(x)) for i in range(self.iter): z += self.coff_function(i + 1) * self.hermite_poly(2 * i + 1, x) * np.exp(-x**2) kpt.f_n[:] = kpt.weight * z n = kpt.f_n.sum() dnde = 1 / np.sqrt(pi) * np.exp(-x**2) for i in range(self.iter): dnde += self.coff_function(i + 1) * self.hermite_poly(2 * i + 2, x) * np.exp(-x**2) dnde = dnde.sum() dnde *= kpt.weight / self.width e_entropy = 0.5 * self.coff_function(self.iter) * self.hermite_poly(2 * self.iter, x)* np.exp(-x**2) e_entropy = kpt.weight * e_entropy.sum() * self.width sign = 1 - kpt.s * 2 return np.array([n, dnde, n * sign, e_entropy])
def screen_potential(r, v, charge, rcut=None, a=None): """Split long-range potential into short-ranged contributions. The potential v is a long-ranted potential with the asymptotic form Z/r corresponding to the given charge. Return a potential vscreened and charge distribution rhocomp such that v(r) = vscreened(r) + vHartree[rhocomp](r). The returned quantities are truncated to a reasonable cutoff radius. """ vr = v * r + charge if rcut is None: err = 0.0 i = len(vr) while err < 1e-4: # Things can be a bit sensitive to the threshold. The O.pz-mt # setup gets 20-30 Bohr long compensation charges if it's 1e-6. i -= 1 err = abs(vr[i]) i += 1 icut = np.searchsorted(r, r[i] * 1.1) else: icut = np.searchsorted(r, rcut) rcut = r[icut] rshort = r[:icut].copy() if rshort[0] < 1e-16: rshort[0] = 1e-10 if a is None: a = rcut / 5.0 # XXX why is this so important? vcomp = np.zeros_like(rshort) vcomp = charge * erf(rshort / (np.sqrt(2.0) * a)) / rshort # XXX divide by r rhocomp = charge * (np.sqrt(2.0 * np.pi) * a)**(-3) * \ np.exp(-0.5 * (rshort / a)**2) vscreened = v[:icut] + vcomp return vscreened, rhocomp
def get_dipole_correction(self, gd, rhot_g): """Get dipole corrections to charge and potential. Returns arrays drhot_g and dphit_g such that if rhot_g has the potential phit_g, then rhot_g + drhot_g has the potential phit_g + dphit_g, where dphit_g is an error function. The error function is chosen so as to be largely constant at the cell boundaries and beyond. """ # This implementation is not particularly economical memory-wise c = self.c # Right now the dipole correction must be along one coordinate # axis and orthogonal to the two others. The two others need not # be orthogonal to each other. for c1 in range(3): if c1 != c: if np.vdot(gd.cell_cv[c], gd.cell_cv[c1]) > 1e-12: raise ValueError('Dipole correction axis must be ' 'orthogonal to the two other axes.') moment = gd.calculate_dipole_moment(rhot_g)[c] if abs(moment) < 1e-12: return gd.zeros(), gd.zeros() r_g = gd.get_grid_point_coordinates()[c] cellsize = abs(gd.cell_cv[c, c]) sr_g = 2.0 / cellsize * r_g - 1.0 # sr ~ 'scaled r' alpha = 12.0 # should perhaps be variable drho_g = sr_g * np.exp(-alpha * sr_g**2) moment2 = gd.calculate_dipole_moment(drho_g)[c] factor = -moment / moment2 drho_g *= factor phifactor = factor * (np.pi / alpha)**1.5 * cellsize**2 / 4.0 dphi_g = -phifactor * erf(sr_g * np.sqrt(alpha)) return drho_g, dphi_g
def __init__(self, cell_cv, nk_c, txt=None): txt = txt or sys.stdout self.nk_c = nk_c bigcell_cv = cell_cv * nk_c[:, np.newaxis] L_c = (np.linalg.inv(bigcell_cv)**2).sum(0)**-0.5 rc = 0.5 * L_c.min() print('Inner radius for %dx%dx%d Wigner-Seitz cell: %.3f Ang' % (tuple(nk_c) + (rc * Bohr,)), file=txt) self.a = 5 / rc print('Range-separation parameter: %.3f Ang^-1' % (self.a / Bohr), file=txt) nr_c = [get_efficient_fft_size(2 * int(L * self.a * 3.0)) for L in L_c] print('FFT size for calculating truncated Coulomb: %dx%dx%d' % tuple(nr_c), file=txt) self.gd = GridDescriptor(nr_c, bigcell_cv, comm=mpi.serial_comm) v_ijk = self.gd.empty() pos_ijkv = self.gd.get_grid_point_coordinates().transpose((1, 2, 3, 0)) corner_xv = np.dot(np.indices((2, 2, 2)).reshape((3, 8)).T, bigcell_cv) # Ignore division by zero (in 0,0,0 corner): with seterr(invalid='ignore'): # Loop over first dimension to avoid too large ndarrays. for pos_jkv, v_jk in zip(pos_ijkv, v_ijk): # Distances to the 8 corners: d_jkxv = pos_jkv[:, :, np.newaxis] - corner_xv r_jk = (d_jkxv**2).sum(axis=3).min(2)**0.5 v_jk[:] = erf(self.a * r_jk) / r_jk # Fix 0/0 corner value: v_ijk[0, 0, 0] = 2 * self.a / pi**0.5 self.K_Q = np.fft.fftn(v_ijk) * self.gd.dv
def load_gauss(self, center=None): """Load compensating charge distribution for charged systems. See Appendix B of A. Held and M. Walter, J. Chem. Phys. 141, 174108 (2014). """ # XXX Check if update is needed (dielectric changed)? epsr, dx_epsr, dy_epsr, dz_epsr = self.dielectric.eps_gradeps gauss = Gaussian(self.gd, center=center) rho_g = gauss.get_gauss(0) phi_g = gauss.get_gauss_pot(0) x, y, z = gauss.xyz fac = 2. * np.sqrt(gauss.a) * np.exp(-gauss.a * gauss.r2) fac /= np.sqrt(np.pi) * gauss.r2 fac -= erf(np.sqrt(gauss.a) * gauss.r) / (gauss.r2 * gauss.r) fac *= 2.0 * 1.7724538509055159 dx_phi_g = fac * x dy_phi_g = fac * y dz_phi_g = fac * z sp = dx_phi_g * dx_epsr + dy_phi_g * dy_epsr + dz_phi_g * dz_epsr rho = epsr * rho_g - 1. / (4. * np.pi) * sp invnorm = np.sqrt(4. * np.pi) / self.gd.integrate(rho) self.phi_gauss = phi_g * invnorm self.rho_gauss = rho * invnorm
epsinf = 80. gauss = Gaussian(gd, center=(box / 2. + epsshift) * np.ones(3) / Bohr) eps = gauss.get_gauss(0) eps = epsinf - eps / eps.max() * (epsinf - 1.) rho = gd.zeros() phi_expected = gd.zeros() grad_eps = gradient(gd, eps - epsinf, nn) for q, shift in zip(qs, shifts): gauss = Gaussian(gd, center=(box / 2. + shift) * np.ones(3) / Bohr) phi_tmp = gauss.get_gauss_pot(0) xyz = gauss.xyz fac = 2. * np.sqrt(gauss.a) * np.exp(-gauss.a * gauss.r2) fac /= np.sqrt(np.pi) * gauss.r2 fac -= erf(np.sqrt(gauss.a) * gauss.r) / (gauss.r2 * gauss.r) fac *= 2.0 * 1.7724538509055159 grad_phi = fac * xyz laplace_phi = -4. * np.pi * gauss.get_gauss(0) rho_tmp = -1. / (4. * np.pi) * ( (grad_eps * grad_phi).sum(0) + eps * laplace_phi) norm = gd.integrate(rho_tmp) rho_tmp /= norm * q phi_tmp /= norm * q rho += rho_tmp phi_expected += phi_tmp # PolarizationPoissonSolver does not pass this test for ps in psolvers[:-1]: phi = solve(ps, eps, rho) parprint(ps, np.abs(phi - phi_expected).max())
def erfc(x): """The complimentary error function.""" return 1. - erf(x)
# 1 0.842701 # 3 0.999978 # I 0. + 1.65043 I # 1 + I 1.31615 + 0.190453 I # 0.3 + 3*I 1467.69 - 166.561 I # 3 - 0.3*I 1.00001 - 0.0000228553 I values = [ [ 1, 0.84270079295+0j], [ 3, 0.999977909503+0j], [ 1j, 1.6504257588j], [ 1+1j, 1.3161512816979477+0.19045346923783471j ], [ 0.3 + 3j, 1467.69028322-166.560924526j ], [ 3 + 0.3j, 0.99997602085736015+2.1863701577230078e-06j]] maxerror = 1.e-10 for test in values: z, res = test try: r = z.real except: r = z error = abs(res / cerf(z) - 1.) if error < maxerror: print 'z=', z, ' ok (error=', error, ')' else: print z, res, cerf(z), erf(r), error string = ('error for erf(' + str(z) +') = ' + str(error) + ' > ' + str(maxerror)) assert(error < maxerror), string
def spillage(alpha): """Fraction of gaussian charge outside rc.""" x = alpha * rc**2 return 1 - erf(sqrt(x)) + 2 * sqrt(x / pi) * exp(-x)
# Mathematica says: # z erf(z) # 1 0.842701 # 3 0.999978 # I 0. + 1.65043 I # 1 + I 1.31615 + 0.190453 I # 0.3 + 3*I 1467.69 - 166.561 I # 3 - 0.3*I 1.00001 - 0.0000228553 I values = [[1, 0.84270079295 + 0j], [3, 0.999977909503 + 0j], [1j, 1.6504257588j], [1 + 1j, 1.3161512816979477 + 0.19045346923783471j], [0.3 + 3j, 1467.69028322 - 166.560924526j], [3 + 0.3j, 0.99997602085736015 + 2.1863701577230078e-06j]] maxerror = 1.e-10 for test in values: z, res = test try: r = z.real except: r = z error = abs(res / cerf(z) - 1.) if error < maxerror: print('z=', z, ' ok (error=', error, ')') else: print(z, res, cerf(z), erf(r), error) string = ('error for erf(' + str(z) + ') = ' + str(error) + ' > ' + str(maxerror)) assert (error < maxerror), string
def __init__(self, calc, xc=None, kpts=None, bands=None, ecut=150.0, alpha=0.0, skip_gamma=False, world=mpi.world, txt=sys.stdout): alpha /= Bohr**2 PairDensity.__init__(self, calc, ecut, world=world, txt=txt) ecut /= Hartree if xc is None: self.exx_fraction = 1.0 xc = XC(XCNull()) if xc == 'PBE0': self.exx_fraction = 0.25 xc = XC('HYB_GGA_XC_PBEH') elif xc == 'B3LYP': self.exx_fraction = 0.2 xc = XC('HYB_GGA_XC_B3LYP') self.xc = xc self.exc = np.nan # density dependent part of xc-energy if kpts is None: # Do all k-points in the IBZ: kpts = range(self.calc.wfs.kd.nibzkpts) if bands is None: # Do all occupied bands: bands = [0, self.nocc2] prnt('Calculating exact exchange contributions for band index', '%d-%d' % (bands[0], bands[1] - 1), file=self.fd) prnt('for IBZ k-points with indices:', ', '.join(str(i) for i in kpts), file=self.fd) self.kpts = kpts self.bands = bands shape = (self.calc.wfs.nspins, len(kpts), bands[1] - bands[0]) self.exxvv_sin = np.zeros(shape) # valence-valence exchange energies self.exxvc_sin = np.zeros(shape) # valence-core exchange energies self.f_sin = np.empty(shape) # occupation numbers # The total EXX energy will not be calculated if we are only # interested in a few eigenvalues for a few k-points self.exx = np.nan # total EXX energy self.exxvv = np.nan # valence-valence self.exxvc = np.nan # valence-core self.exxcc = 0.0 # core-core self.mysKn1n2 = None # my (s, K, n1, n2) indices self.distribute_k_points_and_bands(self.nocc2) # All occupied states: self.mykpts = [self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2] # Compensation charge used for molecular calculations only: self.beta = None # e^(-beta*r^2) self.ngauss_G = None # density self.vgauss_G = None # potential self.G0 = None # effective value for |G+q| when |G+q|=0 self.skip_gamma = skip_gamma if not self.calc.atoms.pbc.any(): # Set exponent of exp-function to -19 on the boundary: self.beta = 4 * 19 * (self.calc.wfs.gd.icell_cv**2).sum(1).max() prnt('Gaussian for electrostatic decoupling: e^(-beta*r^2),', 'beta=%.3f 1/Ang^2' % (self.beta / Bohr**2), file=self.fd) elif skip_gamma: prnt('Skip |G+q|=0 term', file=self.fd) else: # Volume per q-point: dvq = (2 * pi)**3 / self.vol / self.calc.wfs.kd.nbzkpts qcut = (dvq / (4 * pi / 3))**(1 / 3) if alpha == 0.0: self.G0 = (4 * pi * qcut / dvq)**-0.5 else: self.G0 = (2 * pi**1.5 * erf(alpha**0.5 * qcut) / alpha**0.5 / dvq)**-0.5 prnt('G+q=0 term: Integrate e^(-alpha*q^2)/q^2 for', 'q<%.3f 1/Ang and alpha=%.3f Ang^2' % (qcut / Bohr, alpha * Bohr**2), file=self.fd) # PAW matrices: self.V_asii = [] # valence-valence correction self.C_aii = [] # valence-core correction self.initialize_paw_exx_corrections()
def erfc(values): return 1. - erf(values)
# Mathematica says: # z erf(z) # 1 0.842701 # 3 0.999978 # I 0. + 1.65043 I # 1 + I 1.31615 + 0.190453 I # 0.3 + 3*I 1467.69 - 166.561 I # 3 - 0.3*I 1.00001 - 0.0000228553 I values = [[1, 0.84270079295 + 0j], [3, 0.999977909503 + 0j], [1j, 1.6504257588j], [1 + 1j, 1.3161512816979477 + 0.19045346923783471j], [0.3 + 3j, 1467.69028322 - 166.560924526j], [3 + 0.3j, 0.99997602085736015 + 2.1863701577230078e-06j]] maxerror = 1.e-10 for test in values: z, res = test try: r = z.real except: r = z error = abs(res / cerf(z) - 1.) if error < maxerror: print 'z=', z, ' ok (error=', error, ')' else: print z, res, cerf(z), erf(r), error string = ('error for erf(' + str(z) + ') = ' + str(error) + ' > ' + str(maxerror)) assert (error < maxerror), string