def generate(self, zetacount=2, polarizationcount=1, tailnorm=(0.16, 0.3, 0.6), energysplit=0.1, tolerance=1.0e-3, referencefile=None, referenceindex=None, rcutpol_rel=1.0, rcutmax=20.0, #ngaussians=None, rcharpol_rel=None, vconf_args=(12.0, 0.6), txt='-', include_energy_derivatives=False, lvalues=None): """Generate an entire basis set. This is a high-level method which will return a basis set consisting of several different basis vector types. Parameters: ===================== ================================================= ``zetacount`` Number of basis functions per occupied orbital ``polarizationcount`` Number of polarization functions ``tailnorm`` List of tail norms for split-valence scheme ``energysplit`` Energy increase defining confinement radius (eV) ``tolerance`` Tolerance of energy split (eV) ``referencefile`` gpw-file used to generate polarization function ``referenceindex`` Index in reference system of relevant atom ``rcutpol_rel`` Polarization rcut relative to largest other rcut ``rcutmax`` No cutoff will be greater than this value ``vconf_args`` Parameters (alpha, ri/rc) for conf. potential ``txt`` Log filename or '-' for stdout ===================== ================================================= Returns a fully initialized Basis object. """ if txt == '-': txt = sys.stdout elif txt is None: txt = devnull if isinstance(tailnorm, float): tailnorm = (tailnorm,) assert 1 + len(tailnorm) >= max(polarizationcount, zetacount), \ 'Needs %d tail norm values, but only %d are specified' % \ (max(polarizationcount, zetacount) - 1, len(tailnorm)) textbuffer = StringIO() class TeeStream: # Quick hack to both write and save output def __init__(self, out1, out2): self.out1 = out1 self.out2 = out2 def write(self, string): self.out1.write(string) self.out2.write(string) txt = TeeStream(txt, textbuffer) if vconf_args is not None: amplitude, ri_rel = vconf_args g = self.generator rgd = self.rgd # Find out all relevant orbitals # We'll probably need: s, p and d. # The orbitals we want are stored in u_j. # Thus we must find the j corresponding to the highest energy of # each orbital-type. # # However not all orbitals in l_j are actually occupied, so we # will check the occupations in the generator object's lists # # ASSUMPTION: The last index of a given value in l_j corresponds # exactly to the orbital we want, except those which are not occupied # # Get (only) one occupied valence state for each l # Not including polarization in this list if lvalues is None: lvalues = np.unique([l for l, f in zip(g.l_j[g.njcore:], g.f_j[g.njcore:]) if f > 0]) if lvalues[0] != 0: # Always include s-orbital ! lvalues = np.array([0] + list(lvalues)) #print energysplit if isinstance(energysplit,float): energysplit=[energysplit]*(max(lvalues)+1) #print energysplit,'~~~~~~~~' title = '%s Basis functions for %s' % (g.xcname, g.symbol) print >> txt, title print >> txt, '=' * len(title) j_l = {} # index j by l rather than the other way around reversed_l_j = list(g.l_j) reversed_l_j.reverse() # the values we want are stored last for l in lvalues: j = len(reversed_l_j) - reversed_l_j.index(l) - 1 j_l[l] = j singlezetas = [] energy_derivative_functions = [] multizetas = [[] for i in range(zetacount - 1)] polarization_functions = [] splitvalencedescr = 'split-valence wave, fixed tail norm' derivativedescr = 'derivative of sz wrt. (ri/rc) of potential' for l in lvalues: # Get one unmodified pseudo-orbital basis vector for each l j = j_l[l] n = g.n_j[j] orbitaltype = str(n) + 'spdf'[l] msg = 'Basis functions for l=%d, n=%d' % (l, n) print >> txt print >> txt, msg + '\n', '-' * len(msg) print >> txt if vconf_args is None: adverb = 'sharply' else: adverb = 'softly' print >> txt, 'Zeta 1: %s confined pseudo wave,' % adverb, u, e, de, vconf, rc = self.rcut_by_energy(j, energysplit[l], tolerance, vconf_args=vconf_args) if rc > rcutmax: rc = rcutmax # scale things down if vconf is not None: vconf = g.get_confinement_potential(amplitude, ri_rel * rc, rc) u, e = g.solve_confined(j, rc, vconf) print >> txt, 'using maximum cutoff' print >> txt, 'rc=%.02f Bohr' % rc else: print >> txt, 'fixed energy shift' print >> txt, 'DE=%.03f eV :: rc=%.02f Bohr' % (de * Hartree, rc) if vconf is not None: print >> txt, ('Potential amp=%.02f :: ri/rc=%.02f' % (amplitude, ri_rel)) phit_g = self.smoothify(u, l) bf = BasisFunction(l, rc, phit_g, '%s-sz confined orbital' % orbitaltype) norm = np.dot(g.dr, phit_g * phit_g)**.5 print >> txt, 'Norm=%.03f' % norm singlezetas.append(bf) zetacounter = iter(xrange(2, zetacount + 1)) if include_energy_derivatives: assert zetacount > 1 zeta = zetacounter.next() print >> txt, '\nZeta %d: %s' % (zeta, derivativedescr) vconf2 = g.get_confinement_potential(amplitude, ri_rel * rc * .99, rc) u2, e2 = g.solve_confined(j, rc, vconf2) phit2_g = self.smoothify(u2, l) dphit_g = phit2_g - phit_g dphit_norm = np.dot(rgd.dr_g, dphit_g * dphit_g) ** .5 dphit_g /= dphit_norm descr = '%s-dz E-derivative of sz' % orbitaltype bf = BasisFunction(l, rc, dphit_g, descr) energy_derivative_functions.append(bf) for i, zeta in enumerate(zetacounter): # range(zetacount - 1): print >> txt, '\nZeta %d: %s' % (zeta, splitvalencedescr) # Unresolved issue: how does the lack of normalization # of the first function impact the tail norm scheme? # Presumably not much, since most interesting stuff happens # close to the core. rsplit, norm, splitwave = rsplit_by_norm(rgd, l, phit_g, tailnorm[i]**2.0, txt) descr = '%s-%sz split-valence wave' % (orbitaltype, '0sdtq56789'[zeta]) bf = BasisFunction(l, rsplit, phit_g - splitwave, descr) multizetas[i].append(bf) if polarizationcount > 0: # Now make up some properties for the polarization orbital # We just use the cutoffs from the previous one times a factor rcut = max([bf.rc for bf in singlezetas]) * rcutpol_rel rcut = min(rcut, rcutmax) # Find 'missing' values in lvalues for i, l in enumerate(lvalues): if i != l: l_pol = i break else: l_pol = lvalues[-1] + 1 msg = 'Polarization function: l=%d, rc=%.02f' % (l_pol, rcut) print >> txt, '\n' + msg print >> txt, '-' * len(msg) # Make a single Gaussian for polarization function. # # It is known that for given l, the sz cutoff defined # by some fixed energy is strongly correlated to the # value of the characteristic radius which best reproduces # the wave function found by interpolation. # # We know that for e.g. d orbitals: # rchar ~= .37 rcut[sz](.3eV) # Since we don't want to spend a lot of time finding # these value for other energies, we just find the energy # shift at .3 eV now j = max(j_l.values()) u, e, de, vconf, rc_fixed = self.rcut_by_energy(j, .3, 1e-2, 6., (12., .6)) default_rchar_rel = .25 # Defaults for each l. Actually we don't care right now rchar_rels = {} if rcharpol_rel is None: rcharpol_rel = rchar_rels.get(l_pol, default_rchar_rel) rchar = rcharpol_rel * rc_fixed gaussian = QuasiGaussian(1./rchar**2, rcut) psi_pol = gaussian(rgd.r_g) * rgd.r_g**(l_pol + 1) norm = np.dot(rgd.dr_g, psi_pol * psi_pol) ** .5 psi_pol /= norm print >> txt, 'Single quasi Gaussian' msg = 'Rchar = %.03f*rcut = %.03f Bohr' % (rcharpol_rel, rchar) adjective = 'Gaussian' print >> txt, msg #else: # psi_pol = self.make_polarization_function(rcut, l_pol, # referencefile, # referenceindex, # ngaussians, txt) # adjective = 'interpolated' type = '%s-type %s polarization' % ('spdfg'[l_pol], adjective) bf_pol = BasisFunction(l_pol, rcut, psi_pol, type) polarization_functions.append(bf_pol) for i in range(polarizationcount - 1): npol = i + 2 msg = '\n%s: %s' % (['Secondary', 'Tertiary', 'Quaternary', \ 'Quintary', 'Sextary', 'Septenary'][i], splitvalencedescr) print >> txt, msg rsplit, norm, splitwave = rsplit_by_norm(rgd, l_pol, psi_pol, tailnorm[i], txt) descr = ('%s-type split-valence polarization %d' % ('spdfg'[l_pol], npol)) bf_pol = BasisFunction(l_pol, rsplit, psi_pol - splitwave, descr) polarization_functions.append(bf_pol) bf_j = [] bf_j.extend(singlezetas) bf_j.extend(energy_derivative_functions) for multizeta_list in multizetas: bf_j.extend(multizeta_list) bf_j.extend(polarization_functions) rcmax = max([bf.rc for bf in bf_j]) # The non-equidistant grids are really only suited for AE WFs d = 1./64. equidistant_grid = np.arange(0., rcmax + d, d) ng = len(equidistant_grid) for bf in bf_j: # We have been storing phit_g * r, but we just want phit_g bf.phit_g = divrl(bf.phit_g, 1, rgd.r_g) gcut = min(int(1 + bf.rc / d), ng - 1) assert equidistant_grid[gcut] >= bf.rc assert equidistant_grid[gcut - 1] <= bf.rc bf.rc = equidistant_grid[gcut] # Note: bf.rc *must* correspond to a grid point (spline issues) bf.ng = gcut + 1 # XXX all this should be done while building the basis vectors, # not here # Quick hack to change to equidistant coordinates spline = Spline(bf.l, rgd.r_g[rgd.r2g_floor(bf.rc)], bf.phit_g, rgd.r_g, beta=rgd.beta, points=100) bf.phit_g = np.array([spline(r) * r**bf.l for r in equidistant_grid[:bf.ng]]) bf.phit_g[-1] = 0. basis = Basis(g.symbol, self.name, False) basis.ng = ng basis.d = d basis.bf_j = bf_j basis.generatordata = textbuffer.getvalue().strip() basis.generatorattrs = {'version' : version} textbuffer.close() return basis
def generate(self, zetacount=2, polarizationcount=1, tailnorm=(0.16, 0.3, 0.6), energysplit=0.1, tolerance=1.0e-3, referencefile=None, referenceindex=None, rcutpol_rel=1.0, rcutmax=20.0, #ngaussians=None, rcharpol_rel=None, vconf_args=(12.0, 0.6), txt='-', include_energy_derivatives=False, #lvalues=None, # XXX clean up some of these! jvalues=None, l_pol=None ): """Generate an entire basis set. This is a high-level method which will return a basis set consisting of several different basis vector types. Parameters: ===================== ================================================= ``zetacount`` Number of basis functions per occupied orbital ``polarizationcount`` Number of polarization functions ``tailnorm`` List of tail norms for split-valence scheme ``energysplit`` Energy increase defining confinement radius (eV) ``tolerance`` Tolerance of energy split (eV) ``referencefile`` gpw-file used to generate polarization function ``referenceindex`` Index in reference system of relevant atom ``rcutpol_rel`` Polarization rcut relative to largest other rcut ``rcutmax`` No cutoff will be greater than this value ``vconf_args`` Parameters (alpha, ri/rc) for conf. potential ``txt`` Log filename or '-' for stdout ===================== ================================================= Returns a fully initialized Basis object. """ if txt == '-': txt = sys.stdout elif txt is None: txt = devnull if isinstance(tailnorm, float): tailnorm = (tailnorm,) assert 1 + len(tailnorm) >= max(polarizationcount, zetacount), \ 'Needs %d tail norm values, but only %d are specified' % \ (max(polarizationcount, zetacount) - 1, len(tailnorm)) textbuffer = StringIO() class TeeStream: # Quick hack to both write and save output def __init__(self, out1, out2): self.out1 = out1 self.out2 = out2 def write(self, string): self.out1.write(string) self.out2.write(string) txt = TeeStream(txt, textbuffer) if vconf_args is not None: amplitude, ri_rel = vconf_args g = self.generator rgd = self.rgd njcore = g.njcore n_j = g.n_j[njcore:] l_j = g.l_j[njcore:] f_j = g.f_j[njcore:] if jvalues is None: jvalues = [] sortkeys = [] for j in range(len(n_j)): if f_j[j] == 0 and l_j[j] != 0: continue jvalues.append(j) sortkeys.append(l_j[j]) # Now order jvalues by l # # Use a stable sort so the energy ordering within each # angular momentum is guaranteed to be preserved args = np.argsort(sortkeys, kind='mergesort') jvalues = np.array(jvalues)[args] fulljvalues = [njcore + j for j in jvalues] if isinstance(energysplit, float): energysplit = [energysplit] * len(jvalues) title = '%s Basis functions for %s' % (g.xcname, g.symbol) print >> txt, title print >> txt, '=' * len(title) singlezetas = [] energy_derivative_functions = [] multizetas = [[] for i in range(zetacount - 1)] polarization_functions = [] splitvalencedescr = 'split-valence wave, fixed tail norm' derivativedescr = 'derivative of sz wrt. (ri/rc) of potential' for vj, fullj, esplit in zip(jvalues, fulljvalues, energysplit): l = l_j[vj] n = n_j[vj] assert n > 0 orbitaltype = str(n) + 'spdf'[l] msg = 'Basis functions for l=%d, n=%d' % (l, n) print >> txt print >> txt, msg + '\n', '-' * len(msg) print >> txt if vconf_args is None: adverb = 'sharply' else: adverb = 'softly' print >> txt, 'Zeta 1: %s confined pseudo wave,' % adverb, u, e, de, vconf, rc = self.rcut_by_energy(fullj, esplit, tolerance, vconf_args=vconf_args) if rc > rcutmax: rc = rcutmax # scale things down if vconf is not None: vconf = g.get_confinement_potential(amplitude, ri_rel * rc, rc) u, e = g.solve_confined(fullj, rc, vconf) print >> txt, 'using maximum cutoff' print >> txt, 'rc=%.02f Bohr' % rc else: print >> txt, 'fixed energy shift' print >> txt, 'DE=%.03f eV :: rc=%.02f Bohr' % (de * Hartree, rc) if vconf is not None: print >> txt, ('Potential amp=%.02f :: ri/rc=%.02f' % (amplitude, ri_rel)) phit_g = self.smoothify(u, l) bf = BasisFunction(l, rc, phit_g, '%s-sz confined orbital' % orbitaltype) norm = np.dot(g.dr, phit_g * phit_g)**.5 print >> txt, 'Norm=%.03f' % norm singlezetas.append(bf) zetacounter = iter(xrange(2, zetacount + 1)) if include_energy_derivatives: assert zetacount > 1 zeta = zetacounter.next() print >> txt, '\nZeta %d: %s' % (zeta, derivativedescr) vconf2 = g.get_confinement_potential(amplitude, ri_rel * rc * .99, rc) u2, e2 = g.solve_confined(fullj, rc, vconf2) phit2_g = self.smoothify(u2, l) dphit_g = phit2_g - phit_g dphit_norm = np.dot(rgd.dr_g, dphit_g * dphit_g) ** .5 dphit_g /= dphit_norm descr = '%s-dz E-derivative of sz' % orbitaltype bf = BasisFunction(l, rc, dphit_g, descr) energy_derivative_functions.append(bf) for i, zeta in enumerate(zetacounter): # range(zetacount - 1): print >> txt, '\nZeta %d: %s' % (zeta, splitvalencedescr) # Unresolved issue: how does the lack of normalization # of the first function impact the tail norm scheme? # Presumably not much, since most interesting stuff happens # close to the core. rsplit, norm, splitwave = rsplit_by_norm(rgd, l, phit_g, tailnorm[i]**2.0, txt) descr = '%s-%sz split-valence wave' % (orbitaltype, '0sdtq56789'[zeta]) bf = BasisFunction(l, rsplit, phit_g - splitwave, descr) multizetas[i].append(bf) if polarizationcount > 0 or l_pol is not None: if l_pol is None: # Now make up some properties for the polarization orbital # We just use the cutoffs from the previous one times a factor # Find 'missing' values in lvalues lvalues = [l_j[vj] for vj in jvalues] for i in range(max(lvalues) + 1): if list(lvalues).count(i) == 0: l_pol = i break else: l_pol = max(lvalues) + 1 # Find the last state with l=l_pol - 1, which will be the state we # base the polarization function on for vj, fullj, bf in zip(jvalues[::-1], fulljvalues[::-1], singlezetas[::-1]): if bf.l == l_pol - 1: vj_pol = vj # index of the state *which* we polarize fullj_pol = fullj rcut = bf.rc * rcutpol_rel break else: raise ValueError('The requested value l_pol=%d requires l=%d ' 'among valence states' % (l_pol, l_pol - 1)) rcut = min(rcut, rcutmax) msg = 'Polarization function: l=%d, rc=%.02f' % (l_pol, rcut) print >> txt, '\n' + msg print >> txt, '-' * len(msg) # Make a single Gaussian for polarization function. # # It is known that for given l, the sz cutoff defined # by some fixed energy is strongly correlated to the # value of the characteristic radius which best reproduces # the wave function found by interpolation. # # We know that for e.g. d orbitals: # rchar ~= .37 rcut[sz](.3eV) # Since we don't want to spend a lot of time finding # these value for other energies, we just find the energy # shift at .3 eV now u, e, de, vconf, rc_fixed = self.rcut_by_energy(fullj_pol, .3, 1e-2, 6., (12., .6)) default_rchar_rel = .25 # Defaults for each l. Actually we don't care right now rchar_rels = {} if rcharpol_rel is None: rcharpol_rel = rchar_rels.get(l_pol, default_rchar_rel) rchar = rcharpol_rel * rc_fixed gaussian = QuasiGaussian(1./rchar**2, rcut) psi_pol = gaussian(rgd.r_g) * rgd.r_g**(l_pol + 1) norm = np.dot(rgd.dr_g, psi_pol * psi_pol) ** .5 psi_pol /= norm print >> txt, 'Single quasi Gaussian' msg = 'Rchar = %.03f*rcut = %.03f Bohr' % (rcharpol_rel, rchar) adjective = 'Gaussian' print >> txt, msg type = '%s-type %s polarization' % ('spdfg'[l_pol], adjective) bf_pol = BasisFunction(l_pol, rcut, psi_pol, type) polarization_functions.append(bf_pol) for i in range(polarizationcount - 1): npol = i + 2 msg = '\n%s: %s' % (['Secondary', 'Tertiary', 'Quaternary', \ 'Quintary', 'Sextary', 'Septenary'][i], splitvalencedescr) print >> txt, msg rsplit, norm, splitwave = rsplit_by_norm(rgd, l_pol, psi_pol, tailnorm[i], txt) descr = ('%s-type split-valence polarization %d' % ('spdfg'[l_pol], npol)) bf_pol = BasisFunction(l_pol, rsplit, psi_pol - splitwave, descr) polarization_functions.append(bf_pol) bf_j = [] bf_j.extend(singlezetas) bf_j.extend(energy_derivative_functions) for multizeta_list in multizetas: bf_j.extend(multizeta_list) bf_j.extend(polarization_functions) rcmax = max([bf.rc for bf in bf_j]) # The non-equidistant grids are really only suited for AE WFs d = 1./64. equidistant_grid = np.arange(0., rcmax + d, d) ng = len(equidistant_grid) for bf in bf_j: # We have been storing phit_g * r, but we just want phit_g bf.phit_g = divrl(bf.phit_g, 1, rgd.r_g) gcut = min(int(1 + bf.rc / d), ng - 1) assert equidistant_grid[gcut] >= bf.rc assert equidistant_grid[gcut - 1] <= bf.rc bf.rc = equidistant_grid[gcut] # Note: bf.rc *must* correspond to a grid point (spline issues) bf.ng = gcut + 1 # XXX all this should be done while building the basis vectors, # not here # Quick hack to change to equidistant coordinates spline = rgd.spline(bf.phit_g, rgd.r_g[rgd.floor(bf.rc)], bf.l, points=100) bf.phit_g = np.array([spline(r) * r**bf.l for r in equidistant_grid[:bf.ng]]) bf.phit_g[-1] = 0. basis = Basis(g.symbol, self.name, False) basis.ng = ng basis.d = d basis.bf_j = bf_j basis.generatordata = textbuffer.getvalue().strip() basis.generatorattrs = {'version': version} textbuffer.close() return basis
def generate( self, zetacount=2, polarizationcount=1, tailnorm=(0.16, 0.3, 0.6), energysplit=0.1, tolerance=1.0e-3, referencefile=None, referenceindex=None, rcutpol_rel=1.0, rcutmax=20.0, rcharpol_rel=None, vconf_args=(12.0, 0.6), txt='-', include_energy_derivatives=False, # lvalues=None, # XXX clean up some of these! jvalues=None, l_pol=None): """Generate an entire basis set. This is a high-level method which will return a basis set consisting of several different basis vector types. Parameters: ===================== ================================================= ``zetacount`` Number of basis functions per occupied orbital ``polarizationcount`` Number of polarization functions ``tailnorm`` List of tail norms for split-valence scheme ``energysplit`` Energy increase defining confinement radius (eV) ``tolerance`` Tolerance of energy split (eV) ``referencefile`` gpw-file used to generate polarization function ``referenceindex`` Index in reference system of relevant atom ``rcutpol_rel`` Polarization rcut relative to largest other rcut ``rcutmax`` No cutoff will be greater than this value ``vconf_args`` Parameters (alpha, ri/rc) for conf. potential ``txt`` Log filename or '-' for stdout ===================== ================================================= Returns a fully initialized Basis object. """ if txt == '-': txt = sys.stdout elif txt is None: txt = devnull if isinstance(tailnorm, float): tailnorm = (tailnorm, ) if 1 + len(tailnorm) < max(polarizationcount, zetacount): raise ValueError( 'Needs %d tail norm values, but only %d are specified' % (max(polarizationcount, zetacount) - 1, len(tailnorm))) textbuffer = StringIO() class TeeStream: # quick hack to both write and save output def __init__(self, out1, out2): self.out1 = out1 self.out2 = out2 def write(self, string): self.out1.write(string) self.out2.write(string) txt = TeeStream(txt, textbuffer) if vconf_args is not None: amplitude, ri_rel = vconf_args g = self.generator rgd = self.rgd njcore = g.njcore n_j = g.n_j[njcore:] l_j = g.l_j[njcore:] f_j = g.f_j[njcore:] if jvalues is None: jvalues = [] sortkeys = [] for j in range(len(n_j)): if f_j[j] == 0 and l_j[j] != 0: continue jvalues.append(j) sortkeys.append(l_j[j]) # Now order jvalues by l # # Use a stable sort so the energy ordering within each # angular momentum is guaranteed to be preserved args = np.argsort(sortkeys, kind='mergesort') jvalues = np.array(jvalues)[args] fulljvalues = [njcore + j for j in jvalues] if isinstance(energysplit, float): energysplit = [energysplit] * len(jvalues) title = '%s Basis functions for %s' % (g.xcname, g.symbol) print(title, file=txt) print('=' * len(title), file=txt) singlezetas = [] energy_derivative_functions = [] multizetas = [[] for i in range(zetacount - 1)] polarization_functions = [] splitvalencedescr = 'split-valence wave, fixed tail norm' derivativedescr = 'derivative of sz wrt. (ri/rc) of potential' for vj, fullj, esplit in zip(jvalues, fulljvalues, energysplit): l = l_j[vj] n = n_j[vj] assert n > 0 orbitaltype = str(n) + 'spdf'[l] msg = 'Basis functions for l=%d, n=%d' % (l, n) print(file=txt) print(msg + '\n', '-' * len(msg), file=txt) print(file=txt) if vconf_args is None: adverb = 'sharply' else: adverb = 'softly' print('Zeta 1: %s confined pseudo wave,' % adverb, end=' ', file=txt) u, e, de, vconf, rc = self.rcut_by_energy(fullj, esplit, tolerance, vconf_args=vconf_args) if rc > rcutmax: rc = rcutmax # scale things down if vconf is not None: vconf = g.get_confinement_potential( amplitude, ri_rel * rc, rc) u, e = g.solve_confined(fullj, rc, vconf) print('using maximum cutoff', file=txt) print('rc=%.02f Bohr' % rc, file=txt) else: print('fixed energy shift', file=txt) print('DE=%.03f eV :: rc=%.02f Bohr' % (de * Hartree, rc), file=txt) if vconf is not None: print('Potential amp=%.02f :: ri/rc=%.02f' % (amplitude, ri_rel), file=txt) phit_g = self.smoothify(u, l) bf = BasisFunction(n, l, rc, phit_g, '%s-sz confined orbital' % orbitaltype) norm = np.dot(g.dr, phit_g * phit_g)**.5 print('Norm=%.03f' % norm, file=txt) singlezetas.append(bf) zetacounter = iter(range(2, zetacount + 1)) if include_energy_derivatives: assert zetacount > 1 zeta = next(zetacounter) print('\nZeta %d: %s' % (zeta, derivativedescr), file=txt) vconf2 = g.get_confinement_potential(amplitude, ri_rel * rc * .99, rc) u2, e2 = g.solve_confined(fullj, rc, vconf2) phit2_g = self.smoothify(u2, l) dphit_g = phit2_g - phit_g dphit_norm = np.dot(rgd.dr_g, dphit_g * dphit_g)**.5 dphit_g /= dphit_norm descr = '%s-dz E-derivative of sz' % orbitaltype bf = BasisFunction(None, l, rc, dphit_g, descr) energy_derivative_functions.append(bf) for i, zeta in enumerate(zetacounter): print('\nZeta %d: %s' % (zeta, splitvalencedescr), file=txt) # Unresolved issue: how does the lack of normalization # of the first function impact the tail norm scheme? # Presumably not much, since most interesting stuff happens # close to the core. rsplit, norm, splitwave = rsplit_by_norm( rgd, l, phit_g, tailnorm[i]**2.0, txt) descr = '%s-%sz split-valence wave' % (orbitaltype, '0sdtq56789'[zeta]) bf = BasisFunction(None, l, rsplit, phit_g - splitwave, descr) multizetas[i].append(bf) if polarizationcount > 0 or l_pol is not None: if l_pol is None: # Now make up some properties for the polarization orbital # We just use the cutoffs from the previous one times a factor # Find 'missing' values in lvalues lvalues = [l_j[vj] for vj in jvalues] for i in range(max(lvalues) + 1): if list(lvalues).count(i) == 0: l_pol = i break else: l_pol = max(lvalues) + 1 # Find the last state with l=l_pol - 1, which will be the state we # base the polarization function on for vj, fullj, bf in zip(jvalues[::-1], fulljvalues[::-1], singlezetas[::-1]): if bf.l == l_pol - 1: fullj_pol = fullj rcut = bf.rc * rcutpol_rel break else: raise ValueError('The requested value l_pol=%d requires l=%d ' 'among valence states' % (l_pol, l_pol - 1)) rcut = min(rcut, rcutmax) msg = 'Polarization function: l=%d, rc=%.02f' % (l_pol, rcut) print('\n' + msg, file=txt) print('-' * len(msg), file=txt) # Make a single Gaussian for polarization function. # # It is known that for given l, the sz cutoff defined # by some fixed energy is strongly correlated to the # value of the characteristic radius which best reproduces # the wave function found by interpolation. # # We know that for e.g. d orbitals: # rchar ~= .37 rcut[sz](.3eV) # Since we don't want to spend a lot of time finding # these value for other energies, we just find the energy # shift at .3 eV now u, e, de, vconf, rc_fixed = self.rcut_by_energy( fullj_pol, .3, 1e-2, 6., (12., .6)) default_rchar_rel = .25 # Defaults for each l. Actually we don't care right now rchar_rels = {} if rcharpol_rel is None: rcharpol_rel = rchar_rels.get(l_pol, default_rchar_rel) rchar = rcharpol_rel * rc_fixed gaussian = QuasiGaussian(1.0 / rchar**2, rcut) psi_pol = gaussian(rgd.r_g) * rgd.r_g**(l_pol + 1) norm = np.dot(rgd.dr_g, psi_pol * psi_pol)**.5 psi_pol /= norm print('Single quasi Gaussian', file=txt) msg = 'Rchar = %.03f*rcut = %.03f Bohr' % (rcharpol_rel, rchar) adjective = 'Gaussian' print(msg, file=txt) type = '%s-type %s polarization' % ('spdfg'[l_pol], adjective) bf_pol = BasisFunction(None, l_pol, rcut, psi_pol, type) polarization_functions.append(bf_pol) for i in range(polarizationcount - 1): npol = i + 2 msg = '\n%s: %s' % ([ 'Secondary', 'Tertiary', 'Quaternary', 'Quintary', 'Sextary', 'Septenary' ][i], splitvalencedescr) print(msg, file=txt) rsplit, norm, splitwave = rsplit_by_norm( rgd, l_pol, psi_pol, tailnorm[i], txt) descr = ('%s-type split-valence polarization %d' % ('spdfg'[l_pol], npol)) bf_pol = BasisFunction(None, l_pol, rsplit, psi_pol - splitwave, descr) polarization_functions.append(bf_pol) bf_j = [] bf_j.extend(singlezetas) bf_j.extend(energy_derivative_functions) for multizeta_list in multizetas: bf_j.extend(multizeta_list) bf_j.extend(polarization_functions) rcmax = max([bf.rc for bf in bf_j]) # The non-equidistant grids are really only suited for AE WFs d = 1.0 / 64 equidistant_grid = np.arange(0.0, rcmax + d, d) ng = len(equidistant_grid) for bf in bf_j: # We have been storing phit_g * r, but we just want phit_g bf.phit_g = divrl(bf.phit_g, 1, rgd.r_g) gcut = min(int(1 + bf.rc / d), ng - 1) assert equidistant_grid[gcut] >= bf.rc assert equidistant_grid[gcut - 1] <= bf.rc bf.rc = equidistant_grid[gcut] # Note: bf.rc *must* correspond to a grid point (spline issues) bf.ng = gcut + 1 # XXX all this should be done while building the basis vectors, # not here # Quick hack to change to equidistant coordinates spline = rgd.spline(bf.phit_g, rgd.r_g[rgd.floor(bf.rc)], bf.l, points=100) bf.phit_g = np.array( [spline(r) * r**bf.l for r in equidistant_grid[:bf.ng]]) bf.phit_g[-1] = 0. basistype = get_basis_name(zetacount, polarizationcount) if self.name is None: compound_name = basistype else: compound_name = '%s.%s' % (self.name, basistype) basis = Basis(g.symbol, compound_name, False, EquidistantRadialGridDescriptor(d, ng)) basis.bf_j = bf_j basis.generatordata = textbuffer.getvalue().strip() basis.generatorattrs = {'version': version} textbuffer.close() return basis
def generate( self, zetacount=2, polarizationcount=1, tailnorm=(0.16, 0.3, 0.6), energysplit=0.1, tolerance=1.0e-3, referencefile=None, referenceindex=None, rcutpol_rel=1.0, rcutmax=20.0, #ngaussians=None, rcharpol_rel=None, vconf_args=(12.0, 0.6), txt='-', include_energy_derivatives=False, lvalues=None): """Generate an entire basis set. This is a high-level method which will return a basis set consisting of several different basis vector types. Parameters: ===================== ================================================= ``zetacount`` Number of basis functions per occupied orbital ``polarizationcount`` Number of polarization functions ``tailnorm`` List of tail norms for split-valence scheme ``energysplit`` Energy increase defining confinement radius (eV) ``tolerance`` Tolerance of energy split (eV) ``referencefile`` gpw-file used to generate polarization function ``referenceindex`` Index in reference system of relevant atom ``rcutpol_rel`` Polarization rcut relative to largest other rcut ``rcutmax`` No cutoff will be greater than this value ``vconf_args`` Parameters (alpha, ri/rc) for conf. potential ``txt`` Log filename or '-' for stdout ===================== ================================================= Returns a fully initialized Basis object. """ if txt == '-': txt = sys.stdout elif txt is None: txt = devnull if isinstance(tailnorm, float): tailnorm = (tailnorm, ) assert 1 + len(tailnorm) >= max(polarizationcount, zetacount), \ 'Needs %d tail norm values, but only %d are specified' % \ (max(polarizationcount, zetacount) - 1, len(tailnorm)) textbuffer = StringIO() class TeeStream: # Quick hack to both write and save output def __init__(self, out1, out2): self.out1 = out1 self.out2 = out2 def write(self, string): self.out1.write(string) self.out2.write(string) txt = TeeStream(txt, textbuffer) if vconf_args is not None: amplitude, ri_rel = vconf_args g = self.generator rgd = self.rgd # Find out all relevant orbitals # We'll probably need: s, p and d. # The orbitals we want are stored in u_j. # Thus we must find the j corresponding to the highest energy of # each orbital-type. # # However not all orbitals in l_j are actually occupied, so we # will check the occupations in the generator object's lists # # ASSUMPTION: The last index of a given value in l_j corresponds # exactly to the orbital we want, except those which are not occupied # # Get (only) one occupied valence state for each l # Not including polarization in this list if lvalues is None: lvalues = np.unique([ l for l, f in zip(g.l_j[g.njcore:], g.f_j[g.njcore:]) if f > 0 ]) if lvalues[0] != 0: # Always include s-orbital ! lvalues = np.array([0] + list(lvalues)) #print energysplit if isinstance(energysplit, float): energysplit = [energysplit] * (max(lvalues) + 1) #print energysplit,'~~~~~~~~' title = '%s Basis functions for %s' % (g.xcname, g.symbol) print >> txt, title print >> txt, '=' * len(title) j_l = {} # index j by l rather than the other way around reversed_l_j = list(g.l_j) reversed_l_j.reverse() # the values we want are stored last for l in lvalues: j = len(reversed_l_j) - reversed_l_j.index(l) - 1 j_l[l] = j singlezetas = [] energy_derivative_functions = [] multizetas = [[] for i in range(zetacount - 1)] polarization_functions = [] splitvalencedescr = 'split-valence wave, fixed tail norm' derivativedescr = 'derivative of sz wrt. (ri/rc) of potential' for l in lvalues: # Get one unmodified pseudo-orbital basis vector for each l j = j_l[l] n = g.n_j[j] orbitaltype = str(n) + 'spdf'[l] msg = 'Basis functions for l=%d, n=%d' % (l, n) print >> txt print >> txt, msg + '\n', '-' * len(msg) print >> txt if vconf_args is None: adverb = 'sharply' else: adverb = 'softly' print >> txt, 'Zeta 1: %s confined pseudo wave,' % adverb, u, e, de, vconf, rc = self.rcut_by_energy(j, energysplit[l], tolerance, vconf_args=vconf_args) if rc > rcutmax: rc = rcutmax # scale things down if vconf is not None: vconf = g.get_confinement_potential( amplitude, ri_rel * rc, rc) u, e = g.solve_confined(j, rc, vconf) print >> txt, 'using maximum cutoff' print >> txt, 'rc=%.02f Bohr' % rc else: print >> txt, 'fixed energy shift' print >> txt, 'DE=%.03f eV :: rc=%.02f Bohr' % (de * Hartree, rc) if vconf is not None: print >> txt, ('Potential amp=%.02f :: ri/rc=%.02f' % (amplitude, ri_rel)) phit_g = self.smoothify(u, l) bf = BasisFunction(l, rc, phit_g, '%s-sz confined orbital' % orbitaltype) norm = np.dot(g.dr, phit_g * phit_g)**.5 print >> txt, 'Norm=%.03f' % norm singlezetas.append(bf) zetacounter = iter(xrange(2, zetacount + 1)) if include_energy_derivatives: assert zetacount > 1 zeta = zetacounter.next() print >> txt, '\nZeta %d: %s' % (zeta, derivativedescr) vconf2 = g.get_confinement_potential(amplitude, ri_rel * rc * .99, rc) u2, e2 = g.solve_confined(j, rc, vconf2) phit2_g = self.smoothify(u2, l) dphit_g = phit2_g - phit_g dphit_norm = np.dot(rgd.dr_g, dphit_g * dphit_g)**.5 dphit_g /= dphit_norm descr = '%s-dz E-derivative of sz' % orbitaltype bf = BasisFunction(l, rc, dphit_g, descr) energy_derivative_functions.append(bf) for i, zeta in enumerate(zetacounter): # range(zetacount - 1): print >> txt, '\nZeta %d: %s' % (zeta, splitvalencedescr) # Unresolved issue: how does the lack of normalization # of the first function impact the tail norm scheme? # Presumably not much, since most interesting stuff happens # close to the core. rsplit, norm, splitwave = rsplit_by_norm( rgd, l, phit_g, tailnorm[i]**2.0, txt) descr = '%s-%sz split-valence wave' % (orbitaltype, '0sdtq56789'[zeta]) bf = BasisFunction(l, rsplit, phit_g - splitwave, descr) multizetas[i].append(bf) if polarizationcount > 0: # Now make up some properties for the polarization orbital # We just use the cutoffs from the previous one times a factor rcut = max([bf.rc for bf in singlezetas]) * rcutpol_rel rcut = min(rcut, rcutmax) # Find 'missing' values in lvalues for i, l in enumerate(lvalues): if i != l: l_pol = i break else: l_pol = lvalues[-1] + 1 msg = 'Polarization function: l=%d, rc=%.02f' % (l_pol, rcut) print >> txt, '\n' + msg print >> txt, '-' * len(msg) # Make a single Gaussian for polarization function. # # It is known that for given l, the sz cutoff defined # by some fixed energy is strongly correlated to the # value of the characteristic radius which best reproduces # the wave function found by interpolation. # # We know that for e.g. d orbitals: # rchar ~= .37 rcut[sz](.3eV) # Since we don't want to spend a lot of time finding # these value for other energies, we just find the energy # shift at .3 eV now j = max(j_l.values()) u, e, de, vconf, rc_fixed = self.rcut_by_energy( j, .3, 1e-2, 6., (12., .6)) default_rchar_rel = .25 # Defaults for each l. Actually we don't care right now rchar_rels = {} if rcharpol_rel is None: rcharpol_rel = rchar_rels.get(l_pol, default_rchar_rel) rchar = rcharpol_rel * rc_fixed gaussian = QuasiGaussian(1. / rchar**2, rcut) psi_pol = gaussian(rgd.r_g) * rgd.r_g**(l_pol + 1) norm = np.dot(rgd.dr_g, psi_pol * psi_pol)**.5 psi_pol /= norm print >> txt, 'Single quasi Gaussian' msg = 'Rchar = %.03f*rcut = %.03f Bohr' % (rcharpol_rel, rchar) adjective = 'Gaussian' print >> txt, msg #else: # psi_pol = self.make_polarization_function(rcut, l_pol, # referencefile, # referenceindex, # ngaussians, txt) # adjective = 'interpolated' type = '%s-type %s polarization' % ('spdfg'[l_pol], adjective) bf_pol = BasisFunction(l_pol, rcut, psi_pol, type) polarization_functions.append(bf_pol) for i in range(polarizationcount - 1): npol = i + 2 msg = '\n%s: %s' % (['Secondary', 'Tertiary', 'Quaternary', \ 'Quintary', 'Sextary', 'Septenary'][i], splitvalencedescr) print >> txt, msg rsplit, norm, splitwave = rsplit_by_norm( rgd, l_pol, psi_pol, tailnorm[i], txt) descr = ('%s-type split-valence polarization %d' % ('spdfg'[l_pol], npol)) bf_pol = BasisFunction(l_pol, rsplit, psi_pol - splitwave, descr) polarization_functions.append(bf_pol) bf_j = [] bf_j.extend(singlezetas) bf_j.extend(energy_derivative_functions) for multizeta_list in multizetas: bf_j.extend(multizeta_list) bf_j.extend(polarization_functions) rcmax = max([bf.rc for bf in bf_j]) # The non-equidistant grids are really only suited for AE WFs d = 1. / 64. equidistant_grid = np.arange(0., rcmax + d, d) ng = len(equidistant_grid) for bf in bf_j: # We have been storing phit_g * r, but we just want phit_g bf.phit_g = divrl(bf.phit_g, 1, rgd.r_g) gcut = min(int(1 + bf.rc / d), ng - 1) assert equidistant_grid[gcut] >= bf.rc assert equidistant_grid[gcut - 1] <= bf.rc bf.rc = equidistant_grid[gcut] # Note: bf.rc *must* correspond to a grid point (spline issues) bf.ng = gcut + 1 # XXX all this should be done while building the basis vectors, # not here # Quick hack to change to equidistant coordinates spline = Spline(bf.l, rgd.r_g[rgd.r2g_floor(bf.rc)], bf.phit_g, rgd.r_g, beta=rgd.beta, points=100) bf.phit_g = np.array( [spline(r) * r**bf.l for r in equidistant_grid[:bf.ng]]) bf.phit_g[-1] = 0. basis = Basis(g.symbol, self.name, False) basis.ng = ng basis.d = d basis.bf_j = bf_j basis.generatordata = textbuffer.getvalue().strip() basis.generatorattrs = {'version': version} textbuffer.close() return basis