def main(): """Work on it.""" N = 1200 beta = 0.4 * 600 / N rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N) test_same_sto(rgd) test_different_sto(rgd)
def __init__(self, generator, name=None, run=True, gtxt='-', non_relativistic_guess=False, xc='PBE', save_setup=False): if isinstance(generator, str): # treat 'generator' as symbol generator = Generator(generator, scalarrel=True, xcname=xc, txt=gtxt, nofiles=True) generator.N *= 4 self.generator = generator self.rgd = AERadialGridDescriptor(generator.beta / generator.N, 1.0 / generator.N, generator.N, default_spline_points=100) self.name = name if run: if non_relativistic_guess: ae0 = AllElectron(generator.symbol, scalarrel=False, nofiles=False, txt=gtxt, xcname=xc) ae0.N = generator.N ae0.beta = generator.beta ae0.run() # Now files will be stored such that they can # automagically be used by the next run() setup = generator.run(write_xml=False, use_restart_file=False, name=name, **parameters[generator.symbol]) if save_setup: setup.write_xml() else: if save_setup: raise ValueError('cannot save setup here because setup ' 'was already generated before basis ' 'generation.')
def __init__(self, generator, name=None, run=True, gtxt='-', non_relativistic_guess=False, xc='PBE'): if isinstance(generator, str): # treat 'generator' as symbol generator = Generator(generator, scalarrel=True, xcname=xc, txt=gtxt, nofiles=True) generator.N *= 4 self.generator = generator self.rgd = AERadialGridDescriptor(generator.beta / generator.N, 1.0 / generator.N, generator.N, default_spline_points=100) self.name = name if run: if non_relativistic_guess: ae0 = AllElectron(generator.symbol, scalarrel=False, nofiles=False, txt=gtxt, xcname=xc) ae0.N = generator.N ae0.beta = generator.beta ae0.run() # Now files will be stored such that they can # automagically be used by the next run() generator.run(write_xml=False, use_restart_file=False, **parameters[generator.symbol])
def initialize(self, ngpts=2000, rcut=50.0, alpha1=0.01, alpha2=None, ngauss=50, eps=1.0e-7): """Initialize basis sets and radial grid. ngpts: int Number of grid points for radial grid. rcut: float Cutoff for radial grid. alpha1: float Smallest exponent for gaussian. alpha2: float Largest exponent for gaussian. ngauss: int Number of gaussians. eps: float Cutoff for eigenvalues of overlap matrix.""" if alpha2 is None: alpha2 = 50.0 * self.Z**2 # Use grid with r(0)=0, r(1)=a and r(ngpts)=rcut: a = 1 / alpha2**0.5 / 20 b = (rcut - a * ngpts) / (rcut * ngpts) b = 1 / round(1 / b) self.rgd = AERadialGridDescriptor(a, b, ngpts) self.log('Grid points: %d (%.5f, %.5f, %.5f, ..., %.3f, %.3f)' % ((self.rgd.N,) + tuple(self.rgd.r_g[[0, 1, 2, -2, -1]]))) # Distribute exponents between alpha1 and alpha2: alpha_B = alpha1 * (alpha2 / alpha1)**np.linspace(0, 1, ngauss) self.log('Exponents: %d (%.3f, %.3f, ..., %.3f, %.3f)' % ((ngauss,) + tuple(alpha_B[[0, 1, -2, -1]]))) # Maximum l value: lmax = max(self.f_lsn.keys()) self.channels = [] nb_l = [] if not self.dirac: for l in range(lmax + 1): basis = GaussianBasis(l, alpha_B, self.rgd, eps) nb_l.append(len(basis)) for s in range(self.nspins): self.channels.append(Channel(l, s, self.f_lsn[l][s], basis)) else: for K in range(1, lmax + 2): leff = (K**2 - (self.Z / c)**2)**0.5 - 1 basis = GaussianBasis(leff, alpha_B, self.rgd, eps) nb_l.append(len(basis)) for k, l in [(-K, K - 1), (K, K)]: if l > lmax: continue f_n = self.f_lsn[l][0] j = abs(k) - 0.5 f_n = (2 * j + 1) / (4 * l + 2) * np.array(f_n) self.channels.append(DiracChannel(k, f_n, basis)) self.log('Basis functions: %s (%s)' % (', '.join([str(nb) for nb in nb_l]), ', '.join('spdf'[:lmax + 1]))) self.vr_sg = self.rgd.zeros(self.nspins) self.vr_sg[:] = -self.Z
class AllElectronAtom: def __init__(self, symbol, xc='LDA', spinpol=False, dirac=False, configuration=None, log=None): """All-electron calculation for spherically symmetric atom. symbol: str (or int) Chemical symbol (or atomic number). xc: str Name of XC-functional. spinpol: bool If true, do spin-polarized calculation. Default is spin-paired. dirac: bool Solve Dirac equation instead of Schrödinger equation. configuration: list Electronic configuration for symbol, format as in gpaw.atom.configurations log: stream Text output.""" if isinstance(symbol, int): symbol = chemical_symbols[symbol] self.symbol = symbol self.Z = atomic_numbers[symbol] self.nspins = 1 + int(bool(spinpol)) self.dirac = bool(dirac) if configuration is not None: self.configuration = copy.deepcopy(configuration) else: self.configuration = None self.scalar_relativistic = False if isinstance(xc, str): self.xc = XC(xc) else: self.xc = xc self.fd = log or sys.stdout self.vr_sg = None # potential * r self.n_sg = 0.0 # density self.rgd = None # radial grid descriptor # Energies: self.ekin = None self.eeig = None self.eH = None self.eZ = None self.channels = None self.initialize_configuration(self.configuration) self.log('Z: ', self.Z) self.log('Name: ', atomic_names[self.Z]) self.log('Symbol: ', symbol) self.log('XC-functional: ', self.xc.name) self.log('Equation: ', ['Schrödinger', 'Dirac'][self.dirac]) self.method = 'Gaussian basis-set' def log(self, *args, **kwargs): print(file=self.fd, *args, **kwargs) def initialize_configuration(self, configuration=None): self.f_lsn = {} if configuration is None: configuration = configurations[self.symbol][1] for n, l, f, e in configuration: if l not in self.f_lsn: self.f_lsn[l] = [[] for s in range(self.nspins)] if self.nspins == 1: self.f_lsn[l][0].append(f) else: # Use Hund's rule: f0 = min(f, 2 * l + 1) self.f_lsn[l][0].append(f0) self.f_lsn[l][1].append(f - f0) if 0: n = 2 + len(self.f_lsn[2][0]) if self.f_lsn[0][0][n] == 2: self.f_lsn[0][0][n] = 1 self.f_lsn[2][0][n - 3] += 1 def add(self, n, l, df=+1, s=None): """Add (remove) electrons.""" if s is None: if self.nspins == 1: s = 0 else: self.add(n, l, 0.5 * df, 0) self.add(n, l, 0.5 * df, 1) return if l not in self.f_lsn: self.f_lsn[l] = [[] for x in range(self.nspins)] f_n = self.f_lsn[l][s] if len(f_n) < n - l: f_n.extend([0] * (n - l - len(f_n))) f_n[n - l - 1] += df def initialize(self, ngpts=2000, rcut=50.0, alpha1=0.01, alpha2=None, ngauss=50, eps=1.0e-7): """Initialize basis sets and radial grid. ngpts: int Number of grid points for radial grid. rcut: float Cutoff for radial grid. alpha1: float Smallest exponent for gaussian. alpha2: float Largest exponent for gaussian. ngauss: int Number of gaussians. eps: float Cutoff for eigenvalues of overlap matrix.""" if alpha2 is None: alpha2 = 50.0 * self.Z**2 # Use grid with r(0)=0, r(1)=a and r(ngpts)=rcut: a = 1 / alpha2**0.5 / 20 b = (rcut - a * ngpts) / (rcut * ngpts) b = 1 / round(1 / b) self.rgd = AERadialGridDescriptor(a, b, ngpts) self.log('Grid points: %d (%.5f, %.5f, %.5f, ..., %.3f, %.3f)' % ((self.rgd.N,) + tuple(self.rgd.r_g[[0, 1, 2, -2, -1]]))) # Distribute exponents between alpha1 and alpha2: alpha_B = alpha1 * (alpha2 / alpha1)**np.linspace(0, 1, ngauss) self.log('Exponents: %d (%.3f, %.3f, ..., %.3f, %.3f)' % ((ngauss,) + tuple(alpha_B[[0, 1, -2, -1]]))) # Maximum l value: lmax = max(self.f_lsn.keys()) self.channels = [] nb_l = [] if not self.dirac: for l in range(lmax + 1): basis = GaussianBasis(l, alpha_B, self.rgd, eps) nb_l.append(len(basis)) for s in range(self.nspins): self.channels.append(Channel(l, s, self.f_lsn[l][s], basis)) else: for K in range(1, lmax + 2): leff = (K**2 - (self.Z / c)**2)**0.5 - 1 basis = GaussianBasis(leff, alpha_B, self.rgd, eps) nb_l.append(len(basis)) for k, l in [(-K, K - 1), (K, K)]: if l > lmax: continue f_n = self.f_lsn[l][0] j = abs(k) - 0.5 f_n = (2 * j + 1) / (4 * l + 2) * np.array(f_n) self.channels.append(DiracChannel(k, f_n, basis)) self.log('Basis functions: %s (%s)' % (', '.join([str(nb) for nb in nb_l]), ', '.join('spdf'[:lmax + 1]))) self.vr_sg = self.rgd.zeros(self.nspins) self.vr_sg[:] = -self.Z def solve(self): """Diagonalize Schrödinger equation.""" self.eeig = 0.0 for channel in self.channels: if self.method == 'Gaussian basis-set': channel.solve(self.vr_sg[channel.s]) else: channel.solve2(self.vr_sg[channel.s], self.scalar_relativistic, self.Z) self.eeig += channel.get_eigenvalue_sum() def calculate_density(self): """Calculate elctron density and kinetic energy.""" self.n_sg = self.rgd.zeros(self.nspins) for channel in self.channels: self.n_sg[channel.s] += channel.calculate_density() def calculate_electrostatic_potential(self): """Calculate electrostatic potential and energy.""" n_g = self.n_sg.sum(0) self.vHr_g = self.rgd.poisson(n_g) self.eH = 0.5 * self.rgd.integrate(n_g * self.vHr_g, -1) self.eZ = -self.Z * self.rgd.integrate(n_g, -1) def calculate_xc_potential(self): self.vxc_sg = self.rgd.zeros(self.nspins) self.exc = self.xc.calculate_spherical(self.rgd, self.n_sg, self.vxc_sg) def step(self): self.solve() self.calculate_density() self.calculate_electrostatic_potential() self.calculate_xc_potential() self.vr_sg = self.vxc_sg * self.rgd.r_g self.vr_sg += self.vHr_g self.vr_sg -= self.Z self.ekin = (self.eeig - self.rgd.integrate((self.vr_sg * self.n_sg).sum(0), -1)) def run(self, mix=0.4, maxiter=117, dnmax=1e-9): if self.channels is None: self.initialize() if self.dirac: equation = 'Dirac' elif self.scalar_relativistic: equation = 'scalar-relativistic Schrödinger' else: equation = 'non-relativistic Schrödinger' self.log('\nSolving %s equation using %s:' % (equation, self.method)) dn = self.Z vr_old_sg = None n_old_sg = None for iter in range(maxiter): self.log('.', end='') self.fd.flush() if iter > 0: self.vr_sg *= mix self.vr_sg += (1 - mix) * vr_old_sg dn = self.rgd.integrate(abs(self.n_sg - n_old_sg).sum(0)) if dn <= dnmax: self.log('\nConverged in', iter, 'steps') break vr_old_sg = self.vr_sg n_old_sg = self.n_sg self.step() self.summary() if self.method != 'Gaussian basis-set': for channel in self.channels: assert channel.solve2ok if dn > dnmax: raise RuntimeError('Did not converge!') def refine(self): self.method = 'finite difference' self.run(dnmax=1e-6, mix=0.14, maxiter=200) def summary(self): self.write_states() self.write_energies() def write_states(self): self.log('\n state occupation eigenvalue <r>') if self.dirac: self.log(' nl(j) [Hartree] [eV] [Bohr]') else: self.log(' nl [Hartree] [eV] [Bohr]') self.log('-----------------------------------------------------') states = [] for ch in self.channels: for n, f in enumerate(ch.f_n): states.append((ch.e_n[n], ch, n)) states.sort() for e, ch, n in states: name = str(n + ch.l + 1) + ch.name if self.nspins == 2: name += '(%s)' % '+-'[ch.s] n_g = ch.calculate_density(n) rave = self.rgd.integrate(n_g, 1) self.log(' %-7s %6.3f %13.6f %13.5f %6.3f' % (name, ch.f_n[n], e, e * units.Hartree, rave)) def write_energies(self): self.log('\nEnergies: [Hartree] [eV]') self.log('--------------------------------------------') for text, e in [('kinetic ', self.ekin), ('coulomb (e-e)', self.eH), ('coulomb (e-n)', self.eZ), ('xc ', self.exc), ('total ', self.ekin + self.eH + self.eZ + self.exc)]: self.log(' %s %+13.6f %+13.5f' % (text, e, units.Hartree * e)) self.calculate_exx() self.log('\nExact exchange energy: %.6f Hartree, %.5f eV' % (self.exx, self.exx * units.Hartree)) def get_channel(self, l=None, s=0, k=None): if self.dirac: for channel in self.channels: if channel.k == k: return channel else: for channel in self.channels: if channel.l == l and channel.s == s: return channel raise ValueError def get_orbital(self, n, l=None, s=0, k=None): channel = self.get_channel(l, s, k) return channel.basis.expand(channel.C_nb[n]) def plot_wave_functions(self, rc=4.0): import matplotlib.pyplot as plt for ch in self.channels: for n in range(len(ch.f_n)): fr_g = ch.basis.expand(ch.C_nb[n]) * self.rgd.r_g # fr_g = ch.phi_ng[n] name = str(n + ch.l + 1) + ch.name lw = 2 if self.nspins == 2: name += '(%s)' % '+-'[ch.s] if ch.s == 1: lw = 1 if self.dirac and ch.k > 0: lw = 1 ls = ['-', '--', '-.', ':'][ch.l] n_g = ch.calculate_density(n) rave = self.rgd.integrate(n_g, 1) gave = self.rgd.round(rave) fr_g *= np.sign(fr_g[gave], 0) plt.plot(self.rgd.r_g, fr_g, ls=ls, lw=lw, color=colors[n + ch.l], label=name) plt.legend(loc='best') plt.xlabel('r [Bohr]') plt.ylabel('$r\\phi(r)$') plt.axis(xmax=rc) plt.show() def logarithmic_derivative(self, l, energies, rcut): ch = Channel(l) gcut = self.rgd.round(rcut) u_g = self.rgd.empty() logderivs = [] d0 = 42.0 offset = 0 for e in energies: dudr = ch.integrate_outwards(u_g, self.rgd, self.vr_sg[0], gcut, e, self.scalar_relativistic, self.Z)[0] d1 = np.arctan(dudr / u_g[gcut]) / pi + offset if d1 > d0: offset -= 1 d1 -= 1 logderivs.append(d1) d0 = d1 return np.array(logderivs) def calculate_exx(self, s=None): if s is None: self.exx = sum(self.calculate_exx(s) for s in range(self.nspins)) / self.nspins return self.exx states = [] lmax = 0 for ch in self.channels: l = ch.l for n, phi_g in enumerate(ch.phi_ng): f = ch.f_n[n] if f > 0 and ch.s == s: states.append((l, f * self.nspins / 2.0 / (2 * l + 1), phi_g)) if l > lmax: lmax = l G_LLL = gaunt(lmax) exx = 0.0 j1 = 0 for l1, f1, phi1_g in states: f = 1.0 for l2, f2, phi2_g in states[j1:]: n_g = phi1_g * phi2_g for l in range((l1 + l2) % 2, l1 + l2 + 1, 2): G = (G_LLL[l1**2:(l1 + 1)**2, l2**2:(l2 + 1)**2, l**2:(l + 1)**2]**2).sum() vr_g = self.rgd.poisson(n_g, l) e = f * self.rgd.integrate(vr_g * n_g, -1) / 4 / pi exx -= e * G * f1 * f2 f = 2.0 j1 += 1 return exx
def startElement(self, name, attrs): if sys.version_info[0] < 3: attrs.__contains__ = attrs.has_key setup = self.setup if name == 'paw_setup': setup.version = attrs['version'] assert LooseVersion(setup.version) >= '0.4' if name == 'atom': setup.Z = int(attrs['Z']) setup.Nc = float(attrs['core']) setup.Nv = int(attrs['valence']) elif name == 'xc_functional': if attrs['type'] == 'LDA': setup.xcname = 'LDA' else: setup.xcname = attrs['name'] if attrs['type'] == 'OFDFT': setup.orbital_free = True else: assert attrs['type'] == 'GGA' elif name == 'ae_energy': setup.e_total = float(attrs['total']) setup.e_kinetic = float(attrs['kinetic']) setup.e_electrostatic = float(attrs['electrostatic']) setup.e_xc = float(attrs['xc']) elif name == 'core_energy': setup.e_kinetic_core = float(attrs['kinetic']) elif name == 'state': setup.n_j.append(int(attrs.get('n', -1))) setup.l_j.append(int(attrs['l'])) setup.f_j.append(float(attrs.get('f', 0))) setup.eps_j.append(float(attrs['e'])) setup.rcut_j.append(float(attrs.get('rc', -1))) setup.id_j.append(attrs['id']) # Compatibility with old setups: if LooseVersion(setup.version) < '0.6' and setup.f_j[-1] == 0: setup.n_j[-1] = -1 elif name == 'radial_grid': if attrs['eq'] == 'r=a*i/(n-i)': beta = float(attrs['a']) ng = int(attrs['n']) setup.rgd = AERadialGridDescriptor(beta / ng, 1.0 / ng, ng) elif attrs['eq'] == 'r=a*i/(1-b*i)': a = float(attrs['a']) b = float(attrs['b']) N = int(attrs['n']) setup.rgd = AERadialGridDescriptor(a, b, N) else: raise ValueError('Unknown grid:' + attrs['eq']) elif name == 'shape_function': if 'rc' in attrs: assert attrs['type'] == 'gauss' setup.rcgauss = float(attrs['rc']) else: # Old style: XXX setup.rcgauss = max(setup.rcut_j) / sqrt(float(attrs['alpha'])) elif name in [ 'ae_core_density', 'pseudo_core_density', 'localized_potential', 'yukawa_exchange_X_matrix', 'kinetic_energy_differences', 'exact_exchange_X_matrix', 'ae_core_kinetic_energy_density', 'pseudo_core_kinetic_energy_density' ]: self.data = [] elif name.startswith('GLLB_'): self.data = [] elif name in ['ae_partial_wave', 'pseudo_partial_wave']: self.data = [] self.id = attrs['state'] elif name == 'projector_function': self.id = attrs['state'] self.data = [] elif name == 'exact_exchange': setup.ExxC = float(attrs['core-core']) elif name == 'yukawa_exchange': setup.X_gamma = float(attrs['gamma']) elif name == 'core_hole_state': setup.has_corehole = True setup.fcorehole = float(attrs['removed']) setup.lcorehole = 'spdf'.find(attrs['state'][1]) setup.core_hole_e = float(attrs['eig']) setup.core_hole_e_kin = float(attrs['ekin']) self.data = [] elif name == 'zero_potential': if 'type' in attrs: setup.r0 = float(attrs['r0']) setup.nderiv0 = int(attrs['nderiv']) if attrs['type'] == 'polynomial': setup.e0 = None setup.l0 = None else: setup.e0 = float(attrs['e0']) setup.l0 = 'spdfg'.find(attrs['type']) self.data = [] elif name == 'generator': setup.type = attrs['type'] setup.generator_version = int(attrs.get('version', '1')) else: self.data = None
class AllElectronAtom: def __init__(self, symbol, xc='LDA', spinpol=False, dirac=False, log=sys.stdout): """All-electron calculation for spherically symmetric atom. symbol: str (or int) Chemical symbol (or atomic number). xc: str Name of XC-functional. spinpol: bool If true, do spin-polarized calculation. Default is spin-paired. dirac: bool Solve Dirac equation instead of Schrödinger equation. log: stream Text output.""" if isinstance(symbol, int): symbol = chemical_symbols[symbol] self.symbol = symbol self.Z = atomic_numbers[symbol] self.nspins = 1 + int(bool(spinpol)) self.dirac = bool(dirac) self.scalar_relativistic = False if isinstance(xc, str): self.xc = XC(xc) else: self.xc = xc if log is None: log = devnull self.fd = log self.vr_sg = None # potential * r self.n_sg = 0.0 # density self.rgd = None # radial grid descriptor # Energies: self.ekin = None self.eeig = None self.eH = None self.eZ = None self.channels = None self.initialize_configuration() self.log('Z: ', self.Z) self.log('Name: ', atomic_names[self.Z]) self.log('Symbol: ', symbol) self.log('XC-functional: ', self.xc.name) self.log('Equation: ', ['Schrödinger', 'Dirac'][self.dirac]) self.method = 'Gaussian basis-set' def log(self, *args, **kwargs): prnt(file=self.fd, *args, **kwargs) def initialize_configuration(self): self.f_lsn = {} for n, l, f, e in configurations[self.symbol][1]: if l not in self.f_lsn: self.f_lsn[l] = [[] for s in range(self.nspins)] if self.nspins == 1: self.f_lsn[l][0].append(f) else: # Use Hund's rule: f0 = min(f, 2 * l + 1) self.f_lsn[l][0].append(f0) self.f_lsn[l][1].append(f - f0) def add(self, n, l, df=+1, s=None): """Add (remove) electrons.""" if s is None: if self.nspins == 1: s = 0 else: self.add(n, l, 0.5 * df, 0) self.add(n, l, 0.5 * df, 1) return if l not in self.f_lsn: self.f_lsn[l] = [[] for x in range(self.nspins)] f_n = self.f_lsn[l][s] if len(f_n) < n - l: f_n.extend([0] * (n - l - len(f_n))) f_n[n - l - 1] += df def initialize(self, ngpts=2000, rcut=50.0, alpha1=0.01, alpha2=None, ngauss=50, eps=1.0e-7): """Initialize basis sets and radial grid. ngpts: int Number of grid points for radial grid. rcut: float Cutoff for radial grid. alpha1: float Smallest exponent for gaussian. alpha2: float Largest exponent for gaussian. ngauss: int Number of gaussians. eps: float Cutoff for eigenvalues of overlap matrix.""" if alpha2 is None: alpha2 = 50.0 * self.Z**2 # Use grid with r(0)=0, r(1)=a and r(ngpts)=rcut: a = 1 / alpha2**0.5 / 20 b = (rcut - a * ngpts) / (rcut * ngpts) b = 1 / round(1 / b) self.rgd = AERadialGridDescriptor(a, b, ngpts) self.log('Grid points: %d (%.5f, %.5f, %.5f, ..., %.3f, %.3f)' % ((self.rgd.N,) + tuple(self.rgd.r_g[[0, 1, 2, -2, -1]]))) # Distribute exponents between alpha1 and alpha2: alpha_B = alpha1 * (alpha2 / alpha1)**np.linspace(0, 1, ngauss) self.log('Exponents: %d (%.3f, %.3f, ..., %.3f, %.3f)' % ((ngauss,) + tuple(alpha_B[[0, 1, -2, -1]]))) # Maximum l value: lmax = max(self.f_lsn.keys()) self.channels = [] nb_l = [] if not self.dirac: for l in range(lmax + 1): basis = GaussianBasis(l, alpha_B, self.rgd, eps) nb_l.append(len(basis)) for s in range(self.nspins): self.channels.append(Channel(l, s, self.f_lsn[l][s], basis)) else: for K in range(1, lmax + 2): leff = (K**2 - (self.Z / c)**2)**0.5 - 1 basis = GaussianBasis(leff, alpha_B, self.rgd, eps) nb_l.append(len(basis)) for k, l in [(-K, K - 1), (K, K)]: if l > lmax: continue f_n = self.f_lsn[l][0] j = abs(k) - 0.5 f_n = (2 * j + 1) / (4 * l + 2) * np.array(f_n) self.channels.append(DiracChannel(k, f_n, basis)) self.log('Basis functions: %s (%s)' % (', '.join([str(nb) for nb in nb_l]), ', '.join('spdf'[:lmax + 1]))) self.vr_sg = self.rgd.zeros(self.nspins) self.vr_sg[:] = -self.Z def solve(self): """Diagonalize Schrödinger equation.""" self.eeig = 0.0 for channel in self.channels: if self.method == 'Gaussian basis-set': channel.solve(self.vr_sg[channel.s]) else: channel.solve2(self.vr_sg[channel.s], self.scalar_relativistic) self.eeig += channel.get_eigenvalue_sum() def calculate_density(self): """Calculate elctron density and kinetic energy.""" self.n_sg = self.rgd.zeros(self.nspins) for channel in self.channels: self.n_sg[channel.s] += channel.calculate_density() def calculate_electrostatic_potential(self): """Calculate electrostatic potential and energy.""" n_g = self.n_sg.sum(0) self.vHr_g = self.rgd.poisson(n_g) self.eH = 0.5 * self.rgd.integrate(n_g * self.vHr_g, -1) self.eZ = -self.Z * self.rgd.integrate(n_g, -1) def calculate_xc_potential(self): self.vxc_sg = self.rgd.zeros(self.nspins) self.exc = self.xc.calculate_spherical(self.rgd, self.n_sg, self.vxc_sg) def step(self): self.solve() self.calculate_density() self.calculate_electrostatic_potential() self.calculate_xc_potential() self.vr_sg = self.vxc_sg * self.rgd.r_g self.vr_sg += self.vHr_g self.vr_sg -= self.Z self.ekin = (self.eeig - self.rgd.integrate((self.vr_sg * self.n_sg).sum(0), -1)) def run(self, mix=0.4, maxiter=117, dnmax=1e-9): if self.channels is None: self.initialize() if self.dirac: equation = 'Dirac' elif self.scalar_relativistic: equation = 'scalar-relativistic Schrödinger' else: equation = 'non-relativistic Schrödinger' self.log('\nSolving %s equation using %s:' % (equation, self.method)) dn = self.Z for iter in range(maxiter): self.log('.', end='') self.fd.flush() if iter > 0: self.vr_sg *= mix self.vr_sg += (1 - mix) * vr_old_sg dn = self.rgd.integrate(abs(self.n_sg - n_old_sg).sum(0)) if dn <= dnmax: self.log('\nConverged in', iter, 'steps') break vr_old_sg = self.vr_sg n_old_sg = self.n_sg self.step() self.summary() if dn > dnmax: raise RuntimeError('Did not converge!') def refine(self): self.method = 'finite difference' self.run(dnmax=1e-6, mix=0.14, maxiter=200) def summary(self): self.write_states() self.write_energies() def write_states(self): self.log('\n state occupation eigenvalue <r>') if self.dirac: self.log(' nl(j) [Hartree] [eV] [Bohr]') else: self.log(' nl [Hartree] [eV] [Bohr]') self.log('-----------------------------------------------------') states = [] for ch in self.channels: for n, f in enumerate(ch.f_n): states.append((ch.e_n[n], ch, n)) states.sort() for e, ch, n in states: name = str(n + ch.l + 1) + ch.name if self.nspins == 2: name += '(%s)' % '+-'[ch.s] n_g = ch.calculate_density(n) rave = self.rgd.integrate(n_g, 1) self.log(' %-7s %6.3f %13.6f %13.5f %6.3f' % (name, ch.f_n[n], e, e * units.Hartree, rave)) def write_energies(self): self.log('\nEnergies: [Hartree] [eV]') self.log('--------------------------------------------') for text, e in [('kinetic ', self.ekin), ('coulomb (e-e)', self.eH), ('coulomb (e-n)', self.eZ), ('xc ', self.exc), ('total ', self.ekin + self.eH + self.eZ + self.exc)]: self.log(' %s %+13.6f %+13.5f' % (text, e, units.Hartree * e)) self.calculate_exx() self.log('\nExact exchange energy: %.6f Hartree, %.5f eV' % (self.exx, self.exx * units.Hartree)) def get_channel(self, l=None, s=0, k=None): if self.dirac: for channel in self.channels: if channel.k == k: return channel else: for channel in self.channels: if channel.l == l and channel.s == s: return channel raise ValueError def get_orbital(self, n, l=None, s=0, k=None): channel = self.get_channel(l, s, k) return channel.basis.expand(channel.C_nb[n]) def plot_wave_functions(self, rc=4.0): import matplotlib.pyplot as plt for ch in self.channels: for n in range(len(ch.f_n)): fr_g = ch.basis.expand(ch.C_nb[n]) * self.rgd.r_g name = str(n + ch.l + 1) + ch.name lw = 2 if self.nspins == 2: name += '(%s)' % '+-'[ch.s] if ch.s == 1: lw = 1 if self.dirac and ch.k > 0: lw = 1 ls = ['-', '--', '-.', ':'][ch.l] n_g = ch.calculate_density(n) rave = self.rgd.integrate(n_g, 1) gave = self.rgd.round(rave) fr_g *= cmp(fr_g[gave], 0) plt.plot(self.rgd.r_g, fr_g, ls=ls, lw=lw, color=colors[n + ch.l], label=name) plt.legend(loc='best') plt.xlabel('r [Bohr]') plt.ylabel('$r\\phi(r)$') plt.axis(xmax=rc) plt.show() def logarithmic_derivative(self, l, energies, rcut): ch = Channel(l) gcut = self.rgd.round(rcut) u_g = self.rgd.empty() logderivs = [] for e in energies: dudr = ch.integrate_outwards(u_g, self.rgd, self.vr_sg[0], gcut, e, self.scalar_relativistic) logderivs.append(dudr / u_g[gcut]) return logderivs def calculate_exx(self, s=None): if s is None: self.exx = sum(self.calculate_exx(s) for s in range(self.nspins)) / self.nspins return self.exx states = [] lmax = 0 for ch in self.channels: l = ch.l for n, phi_g in enumerate(ch.phi_ng): f = ch.f_n[n] if f > 0 and ch.s == s: states.append((l, f * self.nspins / 2.0 / (2 * l + 1), phi_g)) if l > lmax: lmax = l G_LLL = make_gaunt(lmax) exx = 0.0 j1 = 0 for l1, f1, phi1_g in states: f = 1.0 for l2, f2, phi2_g in states[j1:]: n_g = phi1_g * phi2_g for l in range((l1 + l2) % 2, l1 + l2 + 1, 2): G = (G_LLL[l1**2:(l1 + 1)**2, l2**2:(l2 + 1)**2, l**2:(l + 1)**2]**2).sum() vr_g = self.rgd.poisson(n_g, l) e = f * self.rgd.integrate(vr_g * n_g, -1) / 4 / pi exx -= e * G * f1 * f2 f = 2.0 j1 += 1 return exx
def initialize_setup_data(self): hghdata = self.hghdata beta = 0.1 N = 450 rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N, default_spline_points=100) # rgd = EquidistantRadialGridDescriptor(0.001, 10000) self.rgd = rgd self.Z = hghdata.Z self.Nc = hghdata.Z - hghdata.Nv self.Nv = hghdata.Nv self.rcgauss = np.sqrt(2.0) * hghdata.rloc threshold = 1e-8 if len(hghdata.c_n) > 0: vloc_g = create_local_shortrange_potential(rgd.r_g, hghdata.rloc, hghdata.c_n) gcutvbar, rcutvbar = self.find_cutoff(rgd.r_g, rgd.dr_g, vloc_g, threshold) self.vbar_g = np.sqrt(4.0 * np.pi) * vloc_g[:gcutvbar] else: rcutvbar = 0.5 gcutvbar = rgd.ceil(rcutvbar) self.vbar_g = np.zeros(gcutvbar) nj = sum([v.nn for v in hghdata.v_l]) if nj == 0: nj = 1 # Code assumes nj > 0 elsewhere, we fill out with zeroes if not hghdata.v_l: # No projectors. But the remaining code assumes that everything # has projectors! We'll just add the zero function then hghdata.v_l = [VNonLocal(0, 0.01, [[0.]])] n_j = [] l_j = [] # j ordering is significant, must be nl rather than ln for n, l in self.hghdata.nl_iter(): n_j.append(n + 1) # Note: actual n must be positive! l_j.append(l) assert nj == len(n_j) self.nj = nj self.l_j = l_j self.l_orb_j = l_j self.n_j = n_j self.rcut_j = [] self.pt_jg = [] for n, l in zip(n_j, l_j): # Note: even pseudopotentials without projectors will get one # projector, but the coefficients h_ij should be zero so it # doesn't matter pt_g = create_hgh_projector(rgd.r_g, l, n, hghdata.v_l[l].r0) norm = np.sqrt(np.dot(rgd.dr_g, pt_g**2 * rgd.r_g**2)) assert np.abs(1 - norm) < 1e-5, str(1 - norm) gcut, rcut = self.find_cutoff(rgd.r_g, rgd.dr_g, pt_g, threshold) if rcut < 0.5: rcut = 0.5 gcut = rgd.ceil(rcut) pt_g = pt_g[:gcut].copy() rcut = max(rcut, 0.5) self.rcut_j.append(rcut) self.pt_jg.append(pt_g) # This is the correct magnitude of the otherwise normalized # compensation charge self.Delta0 = -self.Nv / np.sqrt(4.0 * np.pi) f_ln = self.hghdata.get_occupation_numbers() f_j = [0] * nj for j, (n, l) in enumerate(self.hghdata.nl_iter()): try: f_j[j] = f_ln[l][n] except IndexError: pass self.f_ln = f_ln self.f_j = f_j r_g, lcomp, ghat = self.get_compensation_charge_functions() assert lcomp == [0] and len(ghat) == 1 renormalized_ghat = self.Nv / (4.0 * np.pi) * ghat[0] self.Eh_compcharge = get_radial_hartree_energy(r_g, renormalized_ghat)
def initialize_setup_data(self): hghdata = self.hghdata beta = 0.1 N = 450 rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N, default_spline_points=100) #rgd = EquidistantRadialGridDescriptor(0.001, 10000) self.rgd = rgd self.Z = hghdata.Z self.Nc = hghdata.Z - hghdata.Nv self.Nv = hghdata.Nv self.rcgauss = np.sqrt(2.0) * hghdata.rloc threshold = 1e-8 if len(hghdata.c_n) > 0: vloc_g = create_local_shortrange_potential(rgd.r_g, hghdata.rloc, hghdata.c_n) gcutvbar, rcutvbar = self.find_cutoff(rgd.r_g, rgd.dr_g, vloc_g, threshold) self.vbar_g = np.sqrt(4.0 * np.pi) * vloc_g[:gcutvbar] else: rcutvbar = 0.5 gcutvbar = rgd.ceil(rcutvbar) self.vbar_g = np.zeros(gcutvbar) nj = sum([v.nn for v in hghdata.v_l]) if nj == 0: nj = 1 # Code assumes nj > 0 elsewhere, we fill out with zeroes if not hghdata.v_l: # No projectors. But the remaining code assumes that everything # has projectors! We'll just add the zero function then hghdata.v_l = [VNonLocal(0, 0.01, [[0.]])] n_j = [] l_j = [] # j ordering is significant, must be nl rather than ln for n, l in self.hghdata.nl_iter(): n_j.append(n + 1) # Note: actual n must be positive! l_j.append(l) assert nj == len(n_j) self.nj = nj self.l_j = l_j self.l_orb_j = l_j self.n_j = n_j self.rcut_j = [] self.pt_jg = [] for n, l in zip(n_j, l_j): # Note: even pseudopotentials without projectors will get one # projector, but the coefficients h_ij should be zero so it # doesn't matter pt_g = create_hgh_projector(rgd.r_g, l, n, hghdata.v_l[l].r0) norm = np.sqrt(np.dot(rgd.dr_g, pt_g**2 * rgd.r_g**2)) assert np.abs(1 - norm) < 1e-5, str(1 - norm) gcut, rcut = self.find_cutoff(rgd.r_g, rgd.dr_g, pt_g, threshold) if rcut < 0.5: rcut = 0.5 gcut = rgd.ceil(rcut) pt_g = pt_g[:gcut].copy() rcut = max(rcut, 0.5) self.rcut_j.append(rcut) self.pt_jg.append(pt_g) # This is the correct magnitude of the otherwise normalized # compensation charge self.Delta0 = -self.Nv / np.sqrt(4.0 * np.pi) f_ln = self.hghdata.get_occupation_numbers() f_j = [0] * nj for j, (n, l) in enumerate(self.hghdata.nl_iter()): try: f_j[j] = f_ln[l][n] except IndexError: pass self.f_ln = f_ln self.f_j = f_j
class AllElectron: """Object for doing an atomic DFT calculation.""" def __init__(self, symbol, xcname='LDA', scalarrel=True, corehole=None, configuration=None, nofiles=True, txt='-', gpernode=150, orbital_free=False, tw_coeff=1.): """Do an atomic DFT calculation. Example:: a = AllElectron('Fe') a.run() """ if txt is None: txt = devnull elif txt == '-': txt = sys.stdout elif isinstance(txt, str): txt = open(txt, 'w') self.txt = txt self.symbol = symbol self.xcname = xcname self.scalarrel = scalarrel self.nofiles = nofiles # Get reference state: self.Z, nlfe_j = configurations[symbol] # Collect principal quantum numbers, angular momentum quantum # numbers, occupation numbers and eigenvalues (j is a combined # index for n and l): self.n_j = [n for n, l, f, e in nlfe_j] self.l_j = [l for n, l, f, e in nlfe_j] self.f_j = [f for n, l, f, e in nlfe_j] self.e_j = [e for n, l, f, e in nlfe_j] if configuration is not None: j = 0 for conf in configuration.split(','): if conf[0].isdigit(): n = int(conf[0]) l = 'spdf'.find(conf[1]) if len(conf) == 2: f = 1.0 else: f = float(conf[2:]) #try: assert n == self.n_j[j] assert l == self.l_j[j] self.f_j[j] = f #except IndexError: # self.n_j.append(n) # self.l_j.append(l) # self.f_j.append(f) # self.e_j.append(self.e_j[-1]) j += 1 else: j += {'He': 1, 'Ne': 3, 'Ar': 5, 'Kr': 8, 'Xe': 11}[conf] maxnodes = max([n - l - 1 for n, l in zip(self.n_j, self.l_j)]) self.N = (maxnodes + 1) * gpernode self.beta = 0.4 self.orbital_free = orbital_free self.tw_coeff = tw_coeff if self.orbital_free: self.n_j = [1] self.l_j = [0] self.f_j = [self.Z] self.e_j = [self.e_j[-1]] t = self.text t() if scalarrel: t('Scalar-relativistic atomic ', end='') else: t('Atomic ', end='') t('%s calculation for %s (%s, Z=%d)' % (xcname, symbol, atomic_names[self.Z], self.Z)) if corehole is not None: self.ncorehole, self.lcorehole, self.fcorehole = corehole # Find j for core hole and adjust occupation: for j in range(len(self.f_j)): if (self.n_j[j] == self.ncorehole and self.l_j[j] == self.lcorehole): assert self.f_j[j] == 2 * (2 * self.lcorehole + 1) self.f_j[j] -= self.fcorehole self.jcorehole = j break coreholestate = '%d%s' % (self.ncorehole, 'spdf'[self.lcorehole]) t('Core hole in %s state (%s occupation: %.1f)' % (coreholestate, coreholestate, self.f_j[self.jcorehole])) else: self.jcorehole = None self.fcorehole = 0 def text(self, *args, **kwargs): self.txt.write( kwargs.get('sep', ' ').join([str(arg) for arg in args]) + kwargs.get('end', '\n')) def initialize_wave_functions(self): r = self.r dr = self.dr # Initialize with Slater function: for l, e, u in zip(self.l_j, self.e_j, self.u_j): if self.symbol in ['Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au']: a = sqrt(-4.0 * e) else: a = sqrt(-2.0 * e) u[:] = r**(1 + l) * np.exp(-a * r) norm = np.dot(u**2, dr) u *= 1.0 / sqrt(norm) def run(self, use_restart_file=True): # beta g # r = ------, g = 0, 1, ..., N - 1 # N - g # # rN # g = -------- # beta + r t = self.text N = self.N beta = self.beta t(N, 'radial gridpoints.') self.rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N) g = np.arange(N, dtype=float) self.r = self.rgd.r_g self.dr = self.rgd.dr_g self.d2gdr2 = self.rgd.d2gdr2() # Number of orbitals: nj = len(self.n_j) # Radial wave functions multiplied by radius: self.u_j = np.zeros((nj, self.N)) # Effective potential multiplied by radius: self.vr = np.zeros(N) # Electron density: self.n = np.zeros(N) # Always spinpaired nspins=1 self.xc = XC(self.xcname) # Initialize for non-local functionals if self.xc.type == 'GLLB': self.xc.pass_stuff_1d(self) self.xc.initialize_1d() n_j = self.n_j l_j = self.l_j f_j = self.f_j e_j = self.e_j Z = self.Z # nuclear charge r = self.r # radial coordinate dr = self.dr # dr/dg n = self.n # electron density vr = self.vr # effective potential multiplied by r vHr = np.zeros(self.N) self.vXC = np.zeros(self.N) restartfile = '%s/%s.restart' % (tempdir, self.symbol) if self.xc.type == 'GLLB' or not use_restart_file: # Do not start from initial guess when doing # non local XC! # This is because we need wavefunctions as well # on the first iteration. fd = None else: try: fd = open(restartfile, 'rb') except IOError: fd = None else: try: n[:] = pickle.load(fd) except (ValueError, IndexError): fd = None else: norm = np.dot(n * r**2, dr) * 4 * pi if abs(norm - sum(f_j)) > 0.01: fd = None else: t('Using old density for initial guess.') n *= sum(f_j) / norm if fd is None: self.initialize_wave_functions() n[:] = self.calculate_density() bar = '|------------------------------------------------|' t(bar) niter = 0 nitermax = 117 qOK = log(1e-10) mix = 0.4 # orbital_free needs more iterations and coefficient if self.orbital_free: mix = 0.01 nitermax = 2000 e_j[0] /= self.tw_coeff if Z > 10: #help convergence for third row elements mix = 0.002 nitermax = 10000 vrold = None while True: # calculate hartree potential hartree(0, n * r * dr, r, vHr) # add potential from nuclear point charge (v = -Z / r) vHr -= Z # calculated exchange correlation potential and energy self.vXC[:] = 0.0 if self.xc.type == 'GLLB': # Update the potential to self.vXC an the energy to self.Exc Exc = self.xc.get_xc_potential_and_energy_1d(self.vXC) else: Exc = self.xc.calculate_spherical(self.rgd, n.reshape((1, -1)), self.vXC.reshape((1, -1))) # calculate new total Kohn-Sham effective potential and # admix with old version vr[:] = (vHr + self.vXC * r) if self.orbital_free: vr /= self.tw_coeff if niter > 0: vr[:] = mix * vr + (1 - mix) * vrold vrold = vr.copy() # solve Kohn-Sham equation and determine the density change self.solve() dn = self.calculate_density() - n n += dn # estimate error from the square of the density change integrated q = log(np.sum((r * dn)**2)) # print progress bar if niter == 0: q0 = q b0 = 0 else: b = int((q0 - q) / (q0 - qOK) * 50) if b > b0: self.txt.write(bar[b0:min(b, 50)]) self.txt.flush() b0 = b # check if converged and break loop if so if q < qOK: self.txt.write(bar[b0:]) self.txt.flush() break niter += 1 if niter > nitermax: raise RuntimeError('Did not converge!') tau = self.calculate_kinetic_energy_density() t() t('Converged in %d iteration%s.' % (niter, 's'[:niter != 1])) try: fd = open(restartfile, 'wb') except IOError: pass else: pickle.dump(n, fd) try: os.chmod(restartfile, 0o666) except OSError: pass Ekin = 0 for f, e in zip(f_j, e_j): Ekin += f * e e_coulomb = 2 * pi * np.dot(n * r * (vHr - Z), dr) Ekin += -4 * pi * np.dot(n * vr * r, dr) if self.orbital_free: # e and vr are not scaled back # instead Ekin is scaled for total energy (printed and inside setup) Ekin *= self.tw_coeff t() t('Lambda:{0}'.format(self.tw_coeff)) t('Correct eigenvalue:{0}'.format(e_j[0] * self.tw_coeff)) t() t() t('Energy contributions:') t('-------------------------') t('Kinetic: %+13.6f' % Ekin) t('XC: %+13.6f' % Exc) t('Potential: %+13.6f' % e_coulomb) t('-------------------------') t('Total: %+13.6f' % (Ekin + Exc + e_coulomb)) self.ETotal = Ekin + Exc + e_coulomb t() t('state eigenvalue ekin rmax') t('-----------------------------------------------') for m, l, f, e, u in zip(n_j, l_j, f_j, e_j, self.u_j): # Find kinetic energy: k = e - np.sum(( np.where(abs(u) < 1e-160, 0, u)**2 * # XXXNumeric! vr * dr)[1:] / r[1:]) # Find outermost maximum: g = self.N - 4 while u[g - 1] >= u[g]: g -= 1 x = r[g - 1:g + 2] y = u[g - 1:g + 2] A = np.transpose(np.array([x**i for i in range(3)])) c, b, a = np.linalg.solve(A, y) assert a < 0.0 rmax = -0.5 * b / a s = 'spdf'[l] t('%d%s^%-4.1f: %12.6f %12.6f %12.3f' % (m, s, f, e, k, rmax)) t('-----------------------------------------------') t('(units: Bohr and Hartree)') for m, l, u in zip(n_j, l_j, self.u_j): self.write(u, 'ae', n=m, l=l) self.write(n, 'n') self.write(vr, 'vr') self.write(vHr, 'vHr') self.write(self.vXC, 'vXC') self.write(tau, 'tau') self.Ekin = Ekin self.e_coulomb = e_coulomb self.Exc = Exc def write(self, array, name=None, n=None, l=None): if self.nofiles: return if name: name = self.symbol + '.' + name else: name = self.symbol if l is not None: assert n is not None if n > 0: # Bound state: name += '.%d%s' % (n, 'spdf'[l]) else: name += '.x%d%s' % (-n, 'spdf'[l]) f = open(name, 'w') for r, a in zip(self.r, array): print(r, a, file=f) def calculate_density(self): """Return the electron charge density divided by 4 pi""" n = np.dot(self.f_j, np.where(abs(self.u_j) < 1e-160, 0, self.u_j)**2) / (4 * pi) n[1:] /= self.r[1:]**2 n[0] = n[1] return n def calculate_kinetic_energy_density(self): """Return the kinetic energy density""" return self.radial_kinetic_energy_density(self.f_j, self.l_j, self.u_j) def radial_kinetic_energy_density(self, f_j, l_j, u_j): """Kinetic energy density from a restricted set of wf's """ shape = np.shape(u_j[0]) dudr = np.zeros(shape) tau = np.zeros(shape) for f, l, u in zip(f_j, l_j, u_j): self.rgd.derivative(u, dudr) # contribution from angular derivatives if l > 0: tau += f * l * (l + 1) * np.where(abs(u) < 1e-160, 0, u)**2 # contribution from radial derivatives dudr = u - self.r * dudr tau += f * np.where(abs(dudr) < 1e-160, 0, dudr)**2 tau[1:] /= self.r[1:]**4 tau[0] = tau[1] return 0.5 * tau / (4 * pi) def calculate_kinetic_energy_density2(self): """Return the kinetic energy density calculation over R(r)=u(r)/r slower convergence with # of radial grid points for Ekin of H than radial_kinetic_energy_density """ shape = self.u_j.shape[1] R = np.zeros(shape) dRdr = np.zeros(shape) tau = np.zeros(shape) for f, l, u in zip(self.f_j, self.l_j, self.u_j): R[1:] = u[1:] / self.r[1:] if l == 0: # estimate value at origin by Taylor series to first order d1 = self.r[1] d2 = self.r[2] R[0] = .5 * (R[1] + R[2] + (R[1] - R[2]) * (d1 + d2) / (d2 - d1)) else: R[0] = 0 self.rgd.derivative(R, dRdr) # contribution from radial derivatives tau += f * np.where(abs(dRdr) < 1e-160, 0, dRdr)**2 # contribution from angular derivatives if l > 0: R[1:] = R[1:] / self.r[1:] if l == 1: R[0] = R[1] else: R[0] = 0 tau += f * l * (l + 1) * np.where(abs(R) < 1e-160, 0, R)**2 return 0.5 * tau / (4 * pi) def solve(self): """Solve the Schrodinger equation :: 2 d u 1 dv du u l(l + 1) - --- - ---- -- (-- - -) + [-------- + 2M(v - e)] u = 0, 2 2 dr dr r 2 dr 2Mc r where the relativistic mass:: 1 M = 1 - --- (v - e) 2 2c and the fine-structure constant alpha = 1/c = 1/137.036 is set to zero for non-scalar-relativistic calculations. On the logaritmic radial grids defined by:: beta g r = ------, g = 0, 1, ..., N - 1 N - g rN g = --------, r = [0; oo[ beta + r the Schrodinger equation becomes:: 2 d u du --- c + -- c + u c = 0 2 2 dg 1 0 dg with the vectors c0, c1, and c2 defined by:: 2 dg 2 c = -r (--) 2 dr 2 2 d g 2 r dg dv c = - --- r - ---- -- -- 1 2 2 dr dr dr 2Mc 2 r dv c = l(l + 1) + 2M(v - e)r + ---- -- 0 2 dr 2Mc """ r = self.r dr = self.dr vr = self.vr c2 = -(r / dr)**2 c10 = -self.d2gdr2 * r**2 # first part of c1 vector if self.scalarrel: self.r2dvdr = np.zeros(self.N) self.rgd.derivative(vr, self.r2dvdr) self.r2dvdr *= r self.r2dvdr -= vr else: self.r2dvdr = None # solve for each quantum state separately for j, (n, l, e, u) in enumerate(zip(self.n_j, self.l_j, self.e_j, self.u_j)): nodes = n - l - 1 # analytically expected number of nodes delta = -0.2 * e nn, A = shoot(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel) # adjust eigenenergy until u has the correct number of nodes while nn != nodes: diff = np.sign(nn - nodes) while diff == np.sign(nn - nodes): e -= diff * delta nn, A = shoot(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel) delta /= 2 # adjust eigenenergy until u is smooth at the turning point de = 1.0 while abs(de) > 1e-9: norm = np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr) u *= 1.0 / sqrt(norm) de = 0.5 * A / norm x = abs(de / e) if x > 0.1: de *= 0.1 / x e -= de assert e < 0.0 nn, A = shoot(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel) self.e_j[j] = e u *= 1.0 / sqrt(np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr)) def solve_confined(self, j, rc, vconf=None): """Solve the Schroedinger equation in a confinement potential. Solves the Schroedinger equation like the solve method, but with a number of differences. Before invoking this method, run solve() to get initial guesses. Parameters: j: solves only for the state given by j rc: solution cutoff. Solution will be zero outside this. vconf: added to the potential (use this as confinement potential) Returns: a tuple containing the solution u and its energy e. Unlike the solve method, this method will not alter any attributes of this object. """ r = self.r dr = self.dr vr = self.vr.copy() if vconf is not None: vr += vconf * r c2 = -(r / dr)**2 c10 = -self.d2gdr2 * r**2 # first part of c1 vector if j is None: n, l, e, u = 3, 2, -0.15, self.u_j[-1].copy() else: n = self.n_j[j] l = self.l_j[j] e = self.e_j[j] u = self.u_j[j].copy() nn, A = shoot_confined(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel, rc=rc, beta=self.beta) assert nn == n - l - 1 # run() should have been called already # adjust eigenenergy until u is smooth at the turning point de = 1.0 while abs(de) > 1e-9: norm = np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr) u *= 1.0 / sqrt(norm) de = 0.5 * A / norm x = abs(de / e) if x > 0.1: de *= 0.1 / x e -= de assert e < 0.0 nn, A = shoot_confined(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel, rc=rc, beta=self.beta) u *= 1.0 / sqrt(np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr)) return u, e def kin(self, l, u, e=None): # XXX move to Generator r = self.r[1:] dr = self.dr[1:] c0 = 0.5 * l * (l + 1) / r**2 c1 = -0.5 * self.d2gdr2[1:] c2 = -0.5 * dr**-2 if e is not None and self.scalarrel: x = 0.5 * alpha**2 Mr = r * (1.0 + x * e) - x * self.vr[1:] c0 += ( (Mr - r) * (self.vr[1:] - e * r) + 0.5 * x * self.r2dvdr[1:] / Mr) / r**2 c1 -= 0.5 * x * self.r2dvdr[1:] / (Mr * dr * r) fp = c2 + 0.5 * c1 fm = c2 - 0.5 * c1 f0 = c0 - 2 * c2 kr = np.zeros(self.N) kr[1:] = f0 * u[1:] + fm * u[:-1] kr[1:-1] += fp[:-1] * u[2:] kr[0] = 0.0 return kr def r2g(self, r): """Convert radius to index of the radial grid.""" return int(r * self.N / (self.beta + r)) def get_confinement_potential(self, alpha, ri, rc): r"""Create a smooth confinement potential. Returns a (potential) function which is zero inside the radius ri and goes to infinity smoothly at rc, after which point it is nan. The potential is given by:: alpha / rc - ri \ V(r) = -------- exp ( - --------- ) for ri < r < rc rc - r \ r - ri / """ i_ri = self.r2g(ri) i_rc = self.r2g(rc) if self.r[i_rc] == rc: # Avoid division by zero in the odd case that rc coincides # exactly with a grid point (which actually happens sometimes) i_rc -= 1 potential = np.zeros(np.shape(self.r)) r = self.r[i_ri + 1:i_rc + 1] exponent = -(rc - ri) / (r - ri) denom = rc - r value = np.exp(exponent) / denom potential[i_ri + 1:i_rc + 1] = value potential[i_rc + 1:] = np.inf return alpha * potential
def run(self, use_restart_file=True): # beta g # r = ------, g = 0, 1, ..., N - 1 # N - g # # rN # g = -------- # beta + r t = self.text N = self.N beta = self.beta t(N, 'radial gridpoints.') self.rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N) g = np.arange(N, dtype=float) self.r = self.rgd.r_g self.dr = self.rgd.dr_g self.d2gdr2 = self.rgd.d2gdr2() # Number of orbitals: nj = len(self.n_j) # Radial wave functions multiplied by radius: self.u_j = np.zeros((nj, self.N)) # Effective potential multiplied by radius: self.vr = np.zeros(N) # Electron density: self.n = np.zeros(N) # Always spinpaired nspins=1 self.xc = XC(self.xcname) # Initialize for non-local functionals if self.xc.type == 'GLLB': self.xc.pass_stuff_1d(self) self.xc.initialize_1d() n_j = self.n_j l_j = self.l_j f_j = self.f_j e_j = self.e_j Z = self.Z # nuclear charge r = self.r # radial coordinate dr = self.dr # dr/dg n = self.n # electron density vr = self.vr # effective potential multiplied by r vHr = np.zeros(self.N) self.vXC = np.zeros(self.N) restartfile = '%s/%s.restart' % (tempdir, self.symbol) if self.xc.type == 'GLLB' or not use_restart_file: # Do not start from initial guess when doing # non local XC! # This is because we need wavefunctions as well # on the first iteration. fd = None else: try: fd = open(restartfile, 'rb') except IOError: fd = None else: try: n[:] = pickle.load(fd) except (ValueError, IndexError): fd = None else: norm = np.dot(n * r**2, dr) * 4 * pi if abs(norm - sum(f_j)) > 0.01: fd = None else: t('Using old density for initial guess.') n *= sum(f_j) / norm if fd is None: self.initialize_wave_functions() n[:] = self.calculate_density() bar = '|------------------------------------------------|' t(bar) niter = 0 nitermax = 117 qOK = log(1e-10) mix = 0.4 # orbital_free needs more iterations and coefficient if self.orbital_free: mix = 0.01 nitermax = 2000 e_j[0] /= self.tw_coeff if Z > 10: #help convergence for third row elements mix = 0.002 nitermax = 10000 vrold = None while True: # calculate hartree potential hartree(0, n * r * dr, r, vHr) # add potential from nuclear point charge (v = -Z / r) vHr -= Z # calculated exchange correlation potential and energy self.vXC[:] = 0.0 if self.xc.type == 'GLLB': # Update the potential to self.vXC an the energy to self.Exc Exc = self.xc.get_xc_potential_and_energy_1d(self.vXC) else: Exc = self.xc.calculate_spherical(self.rgd, n.reshape((1, -1)), self.vXC.reshape((1, -1))) # calculate new total Kohn-Sham effective potential and # admix with old version vr[:] = (vHr + self.vXC * r) if self.orbital_free: vr /= self.tw_coeff if niter > 0: vr[:] = mix * vr + (1 - mix) * vrold vrold = vr.copy() # solve Kohn-Sham equation and determine the density change self.solve() dn = self.calculate_density() - n n += dn # estimate error from the square of the density change integrated q = log(np.sum((r * dn)**2)) # print progress bar if niter == 0: q0 = q b0 = 0 else: b = int((q0 - q) / (q0 - qOK) * 50) if b > b0: self.txt.write(bar[b0:min(b, 50)]) self.txt.flush() b0 = b # check if converged and break loop if so if q < qOK: self.txt.write(bar[b0:]) self.txt.flush() break niter += 1 if niter > nitermax: raise RuntimeError('Did not converge!') tau = self.calculate_kinetic_energy_density() t() t('Converged in %d iteration%s.' % (niter, 's'[:niter != 1])) try: fd = open(restartfile, 'wb') except IOError: pass else: pickle.dump(n, fd) try: os.chmod(restartfile, 0o666) except OSError: pass Ekin = 0 for f, e in zip(f_j, e_j): Ekin += f * e e_coulomb = 2 * pi * np.dot(n * r * (vHr - Z), dr) Ekin += -4 * pi * np.dot(n * vr * r, dr) if self.orbital_free: # e and vr are not scaled back # instead Ekin is scaled for total energy (printed and inside setup) Ekin *= self.tw_coeff t() t('Lambda:{0}'.format(self.tw_coeff)) t('Correct eigenvalue:{0}'.format(e_j[0] * self.tw_coeff)) t() t() t('Energy contributions:') t('-------------------------') t('Kinetic: %+13.6f' % Ekin) t('XC: %+13.6f' % Exc) t('Potential: %+13.6f' % e_coulomb) t('-------------------------') t('Total: %+13.6f' % (Ekin + Exc + e_coulomb)) self.ETotal = Ekin + Exc + e_coulomb t() t('state eigenvalue ekin rmax') t('-----------------------------------------------') for m, l, f, e, u in zip(n_j, l_j, f_j, e_j, self.u_j): # Find kinetic energy: k = e - np.sum(( np.where(abs(u) < 1e-160, 0, u)**2 * # XXXNumeric! vr * dr)[1:] / r[1:]) # Find outermost maximum: g = self.N - 4 while u[g - 1] >= u[g]: g -= 1 x = r[g - 1:g + 2] y = u[g - 1:g + 2] A = np.transpose(np.array([x**i for i in range(3)])) c, b, a = np.linalg.solve(A, y) assert a < 0.0 rmax = -0.5 * b / a s = 'spdf'[l] t('%d%s^%-4.1f: %12.6f %12.6f %12.3f' % (m, s, f, e, k, rmax)) t('-----------------------------------------------') t('(units: Bohr and Hartree)') for m, l, u in zip(n_j, l_j, self.u_j): self.write(u, 'ae', n=m, l=l) self.write(n, 'n') self.write(vr, 'vr') self.write(vHr, 'vHr') self.write(self.vXC, 'vXC') self.write(tau, 'tau') self.Ekin = Ekin self.e_coulomb = e_coulomb self.Exc = Exc
class AllElectron: """Object for doing an atomic DFT calculation.""" def __init__(self, symbol, xcname='LDA', scalarrel=False, corehole=None, configuration=None, nofiles=True, txt='-', gpernode=150, orbital_free=False, tf_coeff=1.): """Do an atomic DFT calculation. Example:: a = AllElectron('Fe') a.run() """ if txt is None: txt = devnull elif txt == '-': txt = sys.stdout elif isinstance(txt, str): txt = open(txt, 'w') self.txt = txt self.symbol = symbol self.xcname = xcname self.scalarrel = scalarrel self.nofiles = nofiles # Get reference state: self.Z, nlfe_j = configurations[symbol] # Collect principal quantum numbers, angular momentum quantum # numbers, occupation numbers and eigenvalues (j is a combined # index for n and l): self.n_j = [n for n, l, f, e in nlfe_j] self.l_j = [l for n, l, f, e in nlfe_j] self.f_j = [f for n, l, f, e in nlfe_j] self.e_j = [e for n, l, f, e in nlfe_j] if configuration is not None: j = 0 for conf in configuration.split(','): if conf[0].isdigit(): n = int(conf[0]) l = 'spdf'.find(conf[1]) if len(conf) == 2: f = 1.0 else: f = float(conf[2:]) #try: assert n == self.n_j[j] assert l == self.l_j[j] self.f_j[j] = f #except IndexError: # self.n_j.append(n) # self.l_j.append(l) # self.f_j.append(f) # self.e_j.append(self.e_j[-1]) j += 1 else: j += {'He': 1, 'Ne': 3, 'Ar': 5, 'Kr': 8, 'Xe': 11}[conf] maxnodes = max([n - l - 1 for n, l in zip(self.n_j, self.l_j)]) self.N = (maxnodes + 1) * gpernode self.beta = 0.4 self.orbital_free = orbital_free self.tf_coeff = tf_coeff if self.orbital_free: self.n_j = [1] self.l_j = [0] self.f_j = [self.Z] self.e_j = [self.e_j[-1]] t = self.text t() if scalarrel: t('Scalar-relativistic atomic ', end='') else: t('Atomic ', end='') t('%s calculation for %s (%s, Z=%d)' % (xcname, symbol, atomic_names[self.Z], self.Z)) if corehole is not None: self.ncorehole, self.lcorehole, self.fcorehole = corehole # Find j for core hole and adjust occupation: for j in range(len(self.f_j)): if (self.n_j[j] == self.ncorehole and self.l_j[j] == self.lcorehole): assert self.f_j[j] == 2 * (2 * self.lcorehole + 1) self.f_j[j] -= self.fcorehole self.jcorehole = j break coreholestate = '%d%s' % (self.ncorehole, 'spdf'[self.lcorehole]) t('Core hole in %s state (%s occupation: %.1f)' % ( coreholestate, coreholestate, self.f_j[self.jcorehole])) else: self.jcorehole = None self.fcorehole = 0 def text(self, *args, **kwargs): self.txt.write(kwargs.get('sep', ' ').join([str(arg) for arg in args]) + kwargs.get('end', '\n')) def initialize_wave_functions(self): r = self.r dr = self.dr # Initialize with Slater function: for l, e, u in zip(self.l_j, self.e_j, self.u_j): if self.symbol in ['Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au']: a = sqrt(-4.0 * e) else: a = sqrt(-2.0 * e) u[:] = r**(1 + l) * np.exp(-a * r) norm = np.dot(u**2, dr) u *= 1.0 / sqrt(norm) def run(self, use_restart_file=True): # beta g # r = ------, g = 0, 1, ..., N - 1 # N - g # # rN # g = -------- # beta + r t = self.text N = self.N beta = self.beta t(N, 'radial gridpoints.') self.rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N) g = np.arange(N, dtype=float) self.r = self.rgd.r_g self.dr = self.rgd.dr_g self.d2gdr2 = self.rgd.d2gdr2() # Number of orbitals: nj = len(self.n_j) # Radial wave functions multiplied by radius: self.u_j = np.zeros((nj, self.N)) # Effective potential multiplied by radius: self.vr = np.zeros(N) # Electron density: self.n = np.zeros(N) # Always spinpaired nspins=1 self.xc = XC(self.xcname) # Initialize for non-local functionals if self.xc.type == 'GLLB': self.xc.pass_stuff_1d(self) self.xc.initialize_1d() n_j = self.n_j l_j = self.l_j f_j = self.f_j e_j = self.e_j Z = self.Z # nuclear charge r = self.r # radial coordinate dr = self.dr # dr/dg n = self.n # electron density vr = self.vr # effective potential multiplied by r vHr = np.zeros(self.N) self.vXC = np.zeros(self.N) restartfile = '%s/%s.restart' % (tempdir, self.symbol) if self.xc.type == 'GLLB' or not use_restart_file: # Do not start from initial guess when doing # non local XC! # This is because we need wavefunctions as well # on the first iteration. fd = None else: try: fd = open(restartfile, 'r') except IOError: fd = None else: try: n[:] = pickle.load(fd) except (ValueError, IndexError): fd = None else: norm = np.dot(n * r**2, dr) * 4 * pi if abs(norm - sum(f_j)) > 0.01: fd = None else: t('Using old density for initial guess.') n *= sum(f_j) / norm if fd is None: self.initialize_wave_functions() n[:] = self.calculate_density() bar = '|------------------------------------------------|' t(bar) niter = 0 nitermax = 117 qOK = log(1e-10) mix = 0.4 # orbital_free needs more iterations and coefficient if self.orbital_free: #qOK = log(1e-14) e_j[0] /= self.tf_coeff mix = 0.01 nitermax = 1000 vrold = None while True: # calculate hartree potential hartree(0, n * r * dr, r, vHr) # add potential from nuclear point charge (v = -Z / r) vHr -= Z # calculated exchange correlation potential and energy self.vXC[:] = 0.0 if self.xc.type == 'GLLB': # Update the potential to self.vXC an the energy to self.Exc Exc = self.xc.get_xc_potential_and_energy_1d(self.vXC) else: Exc = self.xc.calculate_spherical(self.rgd, n.reshape((1, -1)), self.vXC.reshape((1, -1))) # calculate new total Kohn-Sham effective potential and # admix with old version vr[:] = (vHr + self.vXC * r) / self.tf_coeff if niter > 0: vr[:] = mix * vr + (1 - mix) * vrold vrold = vr.copy() # solve Kohn-Sham equation and determine the density change self.solve() dn = self.calculate_density() - n n += dn # estimate error from the square of the density change integrated q = log(np.sum((r * dn)**2)) # print progress bar if niter == 0: q0 = q b0 = 0 else: b = int((q0 - q) / (q0 - qOK) * 50) if b > b0: self.txt.write(bar[b0:min(b, 50)]) self.txt.flush() b0 = b # check if converged and break loop if so if q < qOK: self.txt.write(bar[b0:]) self.txt.flush() break niter += 1 if niter > nitermax: raise RuntimeError('Did not converge!') tau = self.calculate_kinetic_energy_density() t() t('Converged in %d iteration%s.' % (niter, 's'[:niter != 1])) try: fd = open(restartfile, 'w') except IOError: pass else: pickle.dump(n, fd) try: os.chmod(restartfile, 0666) except OSError: pass Ekin = 0 if self.orbital_free: e_j[0] *= self.tf_coeff vr *= self.tf_coeff for f, e in zip(f_j, e_j): Ekin += f * e Epot = 2 * pi * np.dot(n * r * (vHr - Z), dr) Ekin += -4 * pi * np.dot(n * vr * r, dr) t() t('Energy contributions:') t('-------------------------') t('Kinetic: %+13.6f' % Ekin) t('XC: %+13.6f' % Exc) t('Potential: %+13.6f' % Epot) t('-------------------------') t('Total: %+13.6f' % (Ekin + Exc + Epot)) self.ETotal = Ekin + Exc + Epot t() t('state eigenvalue ekin rmax') t('-----------------------------------------------') for m, l, f, e, u in zip(n_j, l_j, f_j, e_j, self.u_j): # Find kinetic energy: k = e - np.sum((np.where(abs(u) < 1e-160, 0, u)**2 * # XXXNumeric! vr * dr)[1:] / r[1:]) # Find outermost maximum: g = self.N - 4 while u[g - 1] >= u[g]: g -= 1 x = r[g - 1:g + 2] y = u[g - 1:g + 2] A = np.transpose(np.array([x**i for i in range(3)])) c, b, a = np.linalg.solve(A, y) assert a < 0.0 rmax = -0.5 * b / a s = 'spdf'[l] t('%d%s^%-4.1f: %12.6f %12.6f %12.3f' % (m, s, f, e, k, rmax)) t('-----------------------------------------------') t('(units: Bohr and Hartree)') for m, l, u in zip(n_j, l_j, self.u_j): self.write(u, 'ae', n=m, l=l) self.write(n, 'n') self.write(vr, 'vr') self.write(vHr, 'vHr') self.write(self.vXC, 'vXC') self.write(tau, 'tau') self.Ekin = Ekin self.Epot = Epot self.Exc = Exc def write(self, array, name=None, n=None, l=None): if self.nofiles: return if name: name = self.symbol + '.' + name else: name = self.symbol if l is not None: assert n is not None if n > 0: # Bound state: name += '.%d%s' % (n, 'spdf'[l]) else: name += '.x%d%s' % (-n, 'spdf'[l]) f = open(name, 'w') for r, a in zip(self.r, array): print(r, a, file=f) def calculate_density(self): """Return the electron charge density divided by 4 pi""" n = np.dot(self.f_j, np.where(abs(self.u_j) < 1e-160, 0, self.u_j)**2) / (4 * pi) n[1:] /= self.r[1:]**2 n[0] = n[1] return n def calculate_kinetic_energy_density(self): """Return the kinetic energy density""" return self.radial_kinetic_energy_density(self.f_j, self.l_j, self.u_j) def radial_kinetic_energy_density(self, f_j, l_j, u_j): """Kinetic energy density from a restricted set of wf's """ shape = np.shape(u_j[0]) dudr = np.zeros(shape) tau = np.zeros(shape) for f, l, u in zip(f_j, l_j, u_j): self.rgd.derivative(u, dudr) # contribution from angular derivatives if l > 0: tau += f * l * (l + 1) * np.where(abs(u) < 1e-160, 0, u)**2 # contribution from radial derivatives dudr = u - self.r * dudr tau += f * np.where(abs(dudr) < 1e-160, 0, dudr)**2 tau[1:] /= self.r[1:]**4 tau[0] = tau[1] return 0.5 * tau / (4 * pi) def calculate_kinetic_energy_density2(self): """Return the kinetic energy density calculation over R(r)=u(r)/r slower convergence with # of radial grid points for Ekin of H than radial_kinetic_energy_density """ shape = self.u_j.shape[1] R = np.zeros(shape) dRdr = np.zeros(shape) tau = np.zeros(shape) for f, l, u in zip(self.f_j, self.l_j, self.u_j): R[1:] = u[1:] / self.r[1:] if l == 0: # estimate value at origin by Taylor series to first order d1 = self.r[1] d2 = self.r[2] R[0] = .5 * (R[1] + R[2] + (R[1] - R[2]) * (d1 + d2) / (d2 - d1)) else: R[0] = 0 self.rgd.derivative(R, dRdr) # contribution from radial derivatives tau += f * np.where(abs(dRdr) < 1e-160, 0, dRdr)**2 # contribution from angular derivatives if l > 0: R[1:] = R[1:] / self.r[1:] if l == 1: R[0] = R[1] else: R[0] = 0 tau += f * l * (l + 1) * np.where(abs(R) < 1e-160, 0, R)**2 return 0.5 * tau / (4 * pi) def solve(self): """Solve the Schrodinger equation :: 2 d u 1 dv du u l(l + 1) - --- - ---- -- (-- - -) + [-------- + 2M(v - e)] u = 0, 2 2 dr dr r 2 dr 2Mc r where the relativistic mass:: 1 M = 1 - --- (v - e) 2 2c and the fine-structure constant alpha = 1/c = 1/137.036 is set to zero for non-scalar-relativistic calculations. On the logaritmic radial grids defined by:: beta g r = ------, g = 0, 1, ..., N - 1 N - g rN g = --------, r = [0; oo[ beta + r the Schrodinger equation becomes:: 2 d u du --- c + -- c + u c = 0 2 2 dg 1 0 dg with the vectors c0, c1, and c2 defined by:: 2 dg 2 c = -r (--) 2 dr 2 2 d g 2 r dg dv c = - --- r - ---- -- -- 1 2 2 dr dr dr 2Mc 2 r dv c = l(l + 1) + 2M(v - e)r + ---- -- 0 2 dr 2Mc """ r = self.r dr = self.dr vr = self.vr c2 = -(r / dr)**2 c10 = -self.d2gdr2 * r**2 # first part of c1 vector if self.scalarrel: self.r2dvdr = np.zeros(self.N) self.rgd.derivative(vr, self.r2dvdr) self.r2dvdr *= r self.r2dvdr -= vr else: self.r2dvdr = None # solve for each quantum state separately for j, (n, l, e, u) in enumerate(zip(self.n_j, self.l_j, self.e_j, self.u_j)): nodes = n - l - 1 # analytically expected number of nodes delta = -0.2 * e nn, A = shoot(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel) # adjust eigenenergy until u has the correct number of nodes while nn != nodes: diff = cmp(nn, nodes) while diff == cmp(nn, nodes): e -= diff * delta nn, A = shoot(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel) delta /= 2 # adjust eigenenergy until u is smooth at the turning point de = 1.0 while abs(de) > 1e-9: norm = np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr) u *= 1.0 / sqrt(norm) de = 0.5 * A / norm x = abs(de / e) if x > 0.1: de *= 0.1 / x e -= de assert e < 0.0 nn, A = shoot(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel) self.e_j[j] = e u *= 1.0 / sqrt(np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr)) def solve_confined(self, j, rc, vconf=None): """Solve the Schroedinger equation in a confinement potential. Solves the Schroedinger equation like the solve method, but with a number of differences. Before invoking this method, run solve() to get initial guesses. Parameters: j: solves only for the state given by j rc: solution cutoff. Solution will be zero outside this. vconf: added to the potential (use this as confinement potential) Returns: a tuple containing the solution u and its energy e. Unlike the solve method, this method will not alter any attributes of this object. """ r = self.r dr = self.dr vr = self.vr.copy() if vconf is not None: vr += vconf * r c2 = -(r / dr)**2 c10 = -self.d2gdr2 * r**2 # first part of c1 vector if j is None: n, l, e, u = 3, 2, -0.15, self.u_j[-1].copy() else: n = self.n_j[j] l = self.l_j[j] e = self.e_j[j] u = self.u_j[j].copy() nn, A = shoot_confined(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel, rc=rc, beta=self.beta) assert nn == n - l - 1 # run() should have been called already # adjust eigenenergy until u is smooth at the turning point de = 1.0 while abs(de) > 1e-9: norm = np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr) u *= 1.0 / sqrt(norm) de = 0.5 * A / norm x = abs(de / e) if x > 0.1: de *= 0.1 / x e -= de assert e < 0.0 nn, A = shoot_confined(u, l, vr, e, self.r2dvdr, r, dr, c10, c2, self.scalarrel, rc=rc, beta=self.beta) u *= 1.0 / sqrt(np.dot(np.where(abs(u) < 1e-160, 0, u)**2, dr)) return u, e def kin(self, l, u, e=None): # XXX move to Generator r = self.r[1:] dr = self.dr[1:] c0 = 0.5 * l * (l + 1) / r**2 c1 = -0.5 * self.d2gdr2[1:] c2 = -0.5 * dr**-2 if e is not None and self.scalarrel: x = 0.5 * alpha**2 Mr = r * (1.0 + x * e) - x * self.vr[1:] c0 += ((Mr - r) * (self.vr[1:] - e * r) + 0.5 * x * self.r2dvdr[1:] / Mr) / r**2 c1 -= 0.5 * x * self.r2dvdr[1:] / (Mr * dr * r) fp = c2 + 0.5 * c1 fm = c2 - 0.5 * c1 f0 = c0 - 2 * c2 kr = np.zeros(self.N) kr[1:] = f0 * u[1:] + fm * u[:-1] kr[1:-1] += fp[:-1] * u[2:] kr[0] = 0.0 return kr def r2g(self, r): """Convert radius to index of the radial grid.""" return int(r * self.N / (self.beta + r)) def get_confinement_potential(self, alpha, ri, rc): """Create a smooth confinement potential. Returns a (potential) function which is zero inside the radius ri and goes to infinity smoothly at rc, after which point it is nan. The potential is given by:: alpha / rc - ri \ V(r) = -------- exp ( - --------- ) for ri < r < rc rc - r \ r - ri / """ i_ri = self.r2g(ri) i_rc = self.r2g(rc) if self.r[i_rc] == rc: # Avoid division by zero in the odd case that rc coincides # exactly with a grid point (which actually happens sometimes) i_rc -= 1 potential = np.zeros(np.shape(self.r)) r = self.r[i_ri + 1:i_rc + 1] exponent = - (rc - ri) / (r - ri) denom = rc - r value = np.exp(exponent) / denom potential[i_ri + 1:i_rc + 1] = value potential[i_rc + 1:] = np.inf return alpha * potential
def run(self, use_restart_file=True): # beta g # r = ------, g = 0, 1, ..., N - 1 # N - g # # rN # g = -------- # beta + r t = self.text N = self.N beta = self.beta t(N, 'radial gridpoints.') self.rgd = AERadialGridDescriptor(beta / N, 1.0 / N, N) g = np.arange(N, dtype=float) self.r = self.rgd.r_g self.dr = self.rgd.dr_g self.d2gdr2 = self.rgd.d2gdr2() # Number of orbitals: nj = len(self.n_j) # Radial wave functions multiplied by radius: self.u_j = np.zeros((nj, self.N)) # Effective potential multiplied by radius: self.vr = np.zeros(N) # Electron density: self.n = np.zeros(N) # Always spinpaired nspins=1 self.xc = XC(self.xcname) # Initialize for non-local functionals if self.xc.type == 'GLLB': self.xc.pass_stuff_1d(self) self.xc.initialize_1d() n_j = self.n_j l_j = self.l_j f_j = self.f_j e_j = self.e_j Z = self.Z # nuclear charge r = self.r # radial coordinate dr = self.dr # dr/dg n = self.n # electron density vr = self.vr # effective potential multiplied by r vHr = np.zeros(self.N) self.vXC = np.zeros(self.N) restartfile = '%s/%s.restart' % (tempdir, self.symbol) if self.xc.type == 'GLLB' or not use_restart_file: # Do not start from initial guess when doing # non local XC! # This is because we need wavefunctions as well # on the first iteration. fd = None else: try: fd = open(restartfile, 'r') except IOError: fd = None else: try: n[:] = pickle.load(fd) except (ValueError, IndexError): fd = None else: norm = np.dot(n * r**2, dr) * 4 * pi if abs(norm - sum(f_j)) > 0.01: fd = None else: t('Using old density for initial guess.') n *= sum(f_j) / norm if fd is None: self.initialize_wave_functions() n[:] = self.calculate_density() bar = '|------------------------------------------------|' t(bar) niter = 0 nitermax = 117 qOK = log(1e-10) mix = 0.4 # orbital_free needs more iterations and coefficient if self.orbital_free: #qOK = log(1e-14) e_j[0] /= self.tf_coeff mix = 0.01 nitermax = 1000 vrold = None while True: # calculate hartree potential hartree(0, n * r * dr, r, vHr) # add potential from nuclear point charge (v = -Z / r) vHr -= Z # calculated exchange correlation potential and energy self.vXC[:] = 0.0 if self.xc.type == 'GLLB': # Update the potential to self.vXC an the energy to self.Exc Exc = self.xc.get_xc_potential_and_energy_1d(self.vXC) else: Exc = self.xc.calculate_spherical(self.rgd, n.reshape((1, -1)), self.vXC.reshape((1, -1))) # calculate new total Kohn-Sham effective potential and # admix with old version vr[:] = (vHr + self.vXC * r) / self.tf_coeff if niter > 0: vr[:] = mix * vr + (1 - mix) * vrold vrold = vr.copy() # solve Kohn-Sham equation and determine the density change self.solve() dn = self.calculate_density() - n n += dn # estimate error from the square of the density change integrated q = log(np.sum((r * dn)**2)) # print progress bar if niter == 0: q0 = q b0 = 0 else: b = int((q0 - q) / (q0 - qOK) * 50) if b > b0: self.txt.write(bar[b0:min(b, 50)]) self.txt.flush() b0 = b # check if converged and break loop if so if q < qOK: self.txt.write(bar[b0:]) self.txt.flush() break niter += 1 if niter > nitermax: raise RuntimeError('Did not converge!') tau = self.calculate_kinetic_energy_density() t() t('Converged in %d iteration%s.' % (niter, 's'[:niter != 1])) try: fd = open(restartfile, 'w') except IOError: pass else: pickle.dump(n, fd) try: os.chmod(restartfile, 0666) except OSError: pass Ekin = 0 if self.orbital_free: e_j[0] *= self.tf_coeff vr *= self.tf_coeff for f, e in zip(f_j, e_j): Ekin += f * e Epot = 2 * pi * np.dot(n * r * (vHr - Z), dr) Ekin += -4 * pi * np.dot(n * vr * r, dr) t() t('Energy contributions:') t('-------------------------') t('Kinetic: %+13.6f' % Ekin) t('XC: %+13.6f' % Exc) t('Potential: %+13.6f' % Epot) t('-------------------------') t('Total: %+13.6f' % (Ekin + Exc + Epot)) self.ETotal = Ekin + Exc + Epot t() t('state eigenvalue ekin rmax') t('-----------------------------------------------') for m, l, f, e, u in zip(n_j, l_j, f_j, e_j, self.u_j): # Find kinetic energy: k = e - np.sum((np.where(abs(u) < 1e-160, 0, u)**2 * # XXXNumeric! vr * dr)[1:] / r[1:]) # Find outermost maximum: g = self.N - 4 while u[g - 1] >= u[g]: g -= 1 x = r[g - 1:g + 2] y = u[g - 1:g + 2] A = np.transpose(np.array([x**i for i in range(3)])) c, b, a = np.linalg.solve(A, y) assert a < 0.0 rmax = -0.5 * b / a s = 'spdf'[l] t('%d%s^%-4.1f: %12.6f %12.6f %12.3f' % (m, s, f, e, k, rmax)) t('-----------------------------------------------') t('(units: Bohr and Hartree)') for m, l, u in zip(n_j, l_j, self.u_j): self.write(u, 'ae', n=m, l=l) self.write(n, 'n') self.write(vr, 'vr') self.write(vHr, 'vHr') self.write(self.vXC, 'vXC') self.write(tau, 'tau') self.Ekin = Ekin self.Epot = Epot self.Exc = Exc
def __init__(self, data, xc, lmax=0, basis=None, filter=None): self.type = data.name self.HubU = None if not data.is_compatible(xc): raise ValueError('Cannot use %s setup with %s functional' % (data.setupname, xc.get_setup_name())) self.symbol = data.symbol self.data = data self.Nc = data.Nc self.Nv = data.Nv self.Z = data.Z l_j = self.l_j = data.l_j self.l_orb_j = data.l_orb_j n_j = self.n_j = data.n_j self.f_j = data.f_j self.eps_j = data.eps_j nj = self.nj = len(l_j) rcut_j = self.rcut_j = data.rcut_j self.ExxC = data.ExxC self.X_p = data.X_p self.orbital_free = data.orbital_free pt_jg = data.pt_jg phit_jg = data.phit_jg phi_jg = data.phi_jg self.fingerprint = data.fingerprint self.filename = data.filename rgd = self.rgd = data.rgd r_g = rgd.r_g dr_g = rgd.dr_g self.lmax = lmax rcutmax = max(rcut_j) rcut2 = 2 * rcutmax gcut2 = rgd.ceil(rcut2) self.gcut2 = gcut2 self.gcutmin = rgd.ceil(min(rcut_j)) if data.generator_version < 2: # Find Fourier-filter cutoff radius: gcutfilter = data.get_max_projector_cutoff() elif filter: rc = rcutmax filter(rgd, rc, data.vbar_g) for l, pt_g in zip(l_j, pt_jg): filter(rgd, rc, pt_g, l) for l in range(max(l_j) + 1): J = [j for j, lj in enumerate(l_j) if lj == l] A_nn = [[ rgd.integrate(phit_jg[j1] * pt_jg[j2]) / 4 / pi for j1 in J ] for j2 in J] B_nn = np.linalg.inv(A_nn) pt_ng = np.dot(B_nn, [pt_jg[j] for j in J]) for n, j in enumerate(J): pt_jg[j] = pt_ng[n] gcutfilter = data.get_max_projector_cutoff() else: rcutfilter = max(rcut_j) gcutfilter = rgd.ceil(rcutfilter) self.rcutfilter = rcutfilter = r_g[gcutfilter] assert (data.vbar_g[gcutfilter:] == 0).all() ni = 0 i = 0 j = 0 jlL_i = [] for l, n in zip(l_j, n_j): for m in range(2 * l + 1): jlL_i.append((j, l, l**2 + m)) i += 1 j += 1 ni = i self.ni = ni _np = ni * (ni + 1) // 2 self.nq = nq = nj * (nj + 1) // 2 lcut = max(l_j) if 2 * lcut < lmax: lcut = (lmax + 1) // 2 self.lcut = lcut self.B_ii = self.calculate_projector_overlaps(pt_jg) self.fcorehole = data.fcorehole self.lcorehole = data.lcorehole if data.phicorehole_g is not None: if self.lcorehole == 0: self.calculate_oscillator_strengths(phi_jg) else: self.A_ci = None # Construct splines: self.vbar = rgd.spline(data.vbar_g, rcutfilter) rcore, nc_g, nct_g, nct = self.construct_core_densities(data) self.rcore = rcore self.nct = nct # Construct splines for core kinetic energy density: tauct_g = data.tauct_g self.tauct = rgd.spline(tauct_g, self.rcore) self.pt_j = self.create_projectors(rcutfilter) if basis is None: basis = self.create_basis_functions(phit_jg, rcut2, gcut2) phit_j = basis.tosplines() self.phit_j = phit_j self.basis = basis self.nao = 0 for phit in self.phit_j: l = phit.get_angular_momentum_number() self.nao += 2 * l + 1 rgd2 = self.rgd2 = AERadialGridDescriptor(rgd.a, rgd.b, gcut2) r_g = rgd2.r_g dr_g = rgd2.dr_g phi_jg = np.array([phi_g[:gcut2].copy() for phi_g in phi_jg]) phit_jg = np.array([phit_g[:gcut2].copy() for phit_g in phit_jg]) self.nc_g = nc_g = nc_g[:gcut2].copy() self.nct_g = nct_g = nct_g[:gcut2].copy() vbar_g = data.vbar_g[:gcut2].copy() extra_xc_data = dict(data.extra_xc_data) # Cut down the GLLB related extra data for key, item in extra_xc_data.items(): if len(item) == rgd.N: extra_xc_data[key] = item[:gcut2].copy() self.extra_xc_data = extra_xc_data self.phicorehole_g = data.phicorehole_g if self.phicorehole_g is not None: self.phicorehole_g = self.phicorehole_g[:gcut2].copy() T_Lqp = self.calculate_T_Lqp(lcut, nq, _np, nj, jlL_i) (g_lg, n_qg, nt_qg, Delta_lq, self.Lmax, self.Delta_pL, Delta0, self.N0_p) = self.get_compensation_charges(phi_jg, phit_jg, _np, T_Lqp) self.Delta0 = Delta0 self.g_lg = g_lg # Solves the radial poisson equation for density n_g def H(n_g, l): return rgd2.poisson(n_g, l) * r_g * dr_g wnc_g = H(nc_g, l=0) wnct_g = H(nct_g, l=0) self.wg_lg = wg_lg = [H(g_lg[l], l) for l in range(lmax + 1)] wn_lqg = [ np.array([H(n_qg[q], l) for q in range(nq)]) for l in range(2 * lcut + 1) ] wnt_lqg = [ np.array([H(nt_qg[q], l) for q in range(nq)]) for l in range(2 * lcut + 1) ] rdr_g = r_g * dr_g dv_g = r_g * rdr_g A = 0.5 * np.dot(nc_g, wnc_g) A -= sqrt(4 * pi) * self.Z * np.dot(rdr_g, nc_g) mct_g = nct_g + Delta0 * g_lg[0] wmct_g = wnct_g + Delta0 * wg_lg[0] A -= 0.5 * np.dot(mct_g, wmct_g) self.M = A self.MB = -np.dot(dv_g * nct_g, vbar_g) AB_q = -np.dot(nt_qg, dv_g * vbar_g) self.MB_p = np.dot(AB_q, T_Lqp[0]) # Correction for average electrostatic potential: # # dEH = dEH0 + dot(D_p, dEH_p) # self.dEH0 = sqrt(4 * pi) * (wnc_g - wmct_g - sqrt(4 * pi) * self.Z * r_g * dr_g).sum() dEh_q = (wn_lqg[0].sum(1) - wnt_lqg[0].sum(1) - Delta_lq[0] * wg_lg[0].sum()) self.dEH_p = np.dot(dEh_q, T_Lqp[0]) * sqrt(4 * pi) M_p, M_pp = self.calculate_coulomb_corrections(lcut, n_qg, wn_lqg, lmax, Delta_lq, wnt_lqg, g_lg, wg_lg, nt_qg, _np, T_Lqp, nc_g, wnc_g, rdr_g, mct_g, wmct_g) self.M_p = M_p self.M_pp = M_pp if xc.type == 'GLLB': if 'core_f' in self.extra_xc_data: self.wnt_lqg = wnt_lqg self.wn_lqg = wn_lqg self.fc_j = self.extra_xc_data['core_f'] self.lc_j = self.extra_xc_data['core_l'] self.njcore = len(self.lc_j) if self.njcore > 0: self.uc_jg = self.extra_xc_data['core_states'].reshape( (self.njcore, -1)) self.uc_jg = self.uc_jg[:, :gcut2] self.phi_jg = phi_jg self.Kc = data.e_kinetic_core - data.e_kinetic self.M -= data.e_electrostatic self.E = data.e_total Delta0_ii = unpack(self.Delta_pL[:, 0].copy()) self.dO_ii = data.get_overlap_correction(Delta0_ii) self.dC_ii = self.get_inverse_overlap_coefficients( self.B_ii, self.dO_ii) self.Delta_iiL = np.zeros((ni, ni, self.Lmax)) for L in range(self.Lmax): self.Delta_iiL[:, :, L] = unpack(self.Delta_pL[:, L].copy()) self.Nct = data.get_smooth_core_density_integral(Delta0) self.K_p = data.get_linear_kinetic_correction(T_Lqp[0]) r = 0.02 * rcut2 * np.arange(51, dtype=float) alpha = data.rcgauss**-2 self.ghat_l = data.get_ghat(lmax, alpha, r, rcut2) self.rcgauss = data.rcgauss self.xc_correction = data.get_xc_correction(rgd2, xc, gcut2, lcut) self.nabla_iiv = self.get_derivative_integrals(rgd2, phi_jg, phit_jg) self.rnabla_iiv = self.get_magnetic_integrals(rgd2, phi_jg, phit_jg) try: from gpaw.lrtddft2.rxnabla import get_magnetic_integrals_new self.rxnabla_iiv = get_magnetic_integrals_new( self, rgd2, phi_jg, phit_jg) except NotImplementedError: self.rxnabla_iiv = None