def outer_scf(self): """ Solve the self-consistent potential. """ self.timer.start('outer_scf') print('\nStart iteration...', file=self.txt) enl = {nl: 0. for n, l, nl in self.list_states()} d_enl = {nl: 0. for n, l, nl in self.list_states()} dens = self.guess_density() veff = self.nuclear_potential(self.rgrid) veff += self.confinement(self.rgrid) for it in range(self.maxiter): veff *= 1. - self.mix veff += self.mix * self.calculate_veff(dens) dveff = None if self.scalarrel: spl = CubicSplineFunction(self.rgrid, veff) dveff = spl(self.rgrid, der=1) itmax, enl, d_enl, unlg, Rnlg = self.inner_scf(it, veff, enl, d_enl, dveff=dveff) dens0 = dens.copy() dens = self.calculate_density(unlg) diff = self.grid.integrate(np.abs(dens - dens0), use_dV=True) if diff < self.convergence['density'] and it > 5: d_enl_max = max(d_enl.values()) if d_enl_max < self.convergence['energies']: break if np.mod(it, 10) == 0: line = 'iter %3i, dn=%.1e>%.1e, max %i sp-iter' % \ (it, diff, self.convergence['density'], itmax) print(line, file=self.txt, flush=True) if it == self.maxiter - 1: if self.timing: self.timer.summary() err = 'Density not converged in %i iterations' % (it + 1) raise RuntimeError(err) self.calculate_energies(enl, dens, echo='valence') print('converged in %i iterations' % it, file=self.txt) nel = self.get_number_of_electrons() line = '%9.4f electrons, should be %9.4f' % \ (self.grid.integrate(dens, use_dV=True), nel) print(line, file=self.txt, flush=True) self.timer.stop('outer_scf') return dens, veff, enl, unlg, Rnlg
def inner_scf(self, iteration, veff, enl, d_enl, dveff=None, itmax=100, solve='all'): """ Solve the eigenstates for given effective potential. u''(r) - 2*(v_eff(r)+l*(l+1)/(2r**2)-e)*u(r)=0 ( u''(r) + c0(r)*u(r) = 0 ) r=r0*exp(x) --> (to get equally spaced integration mesh) u''(x) - u'(x) + c0(x(r))*u(r) = 0 Parameters: iteration: iteration number in the SCF cycle itmax: maximum number of optimization steps per eigenstate solve: which eigenstates to solve: solve='all' -> all states; solve = [nl1, nl2, ...] -> only the given subset """ self.timer.start('inner_scf') if self.scalarrel and dveff is None: spl = CubicSplineFunction(self.rgrid, veff) dveff = spl(self.rgrid, der=1) elif not self.scalarrel: dveff = np.array([]) rgrid = self.rgrid xgrid = self.xgrid dx = xgrid[1] - xgrid[0] N = self.N unlg, Rnlg = {}, {} for n, l, nl in self.list_states(): if solve != 'all' and nl not in solve: continue nodes_nl = n - l - 1 if iteration == 0: eps = -1.0 * self.Z ** 2 / n ** 2 else: eps = enl[nl] if iteration <= 3: delta = 0.5 * self.Z ** 2 / n ** 2 # previous!!!!!!!!!! else: delta = d_enl[nl] direction = 'none' epsmax = veff[-1] - l * (l + 1) / (2 * self.rgrid[-1] ** 2) it = 0 u = np.zeros(N) hist = [] while True: eps0 = eps self.timer.start('coeff') if _hotcent is not None: c0, c1, c2 = _hotcent.construct_coefficients(l, eps, veff, dveff, self.rgrid) else: c0, c1, c2 = self.construct_coefficients(l, eps, veff, dveff=dveff) assert c0[-2] < 0 and c0[-1] < 0 self.timer.stop('coeff') # boundary conditions for integration from analytic behaviour # (unscaled) # u(r)~r**(l+1) r->0 # u(r)~exp( -sqrt(c0(r)) ) (set u[-1]=1 # and use expansion to avoid overflows) u[0:2] = rgrid[0:2] ** (l + 1) self.timer.start('shoot') if _hotcent is not None: u, nodes, A, ctp = _hotcent.shoot(u, dx, c2, c1, c0, N) else: u, nodes, A, ctp = shoot(u, dx, c2, c1, c0, N) self.timer.stop('shoot') self.timer.start('norm') norm = self.grid.integrate(u ** 2) u /= np.sqrt(norm) self.timer.stop('norm') if nodes > nodes_nl: # decrease energy if direction == 'up': delta /= 2 eps -= delta direction = 'down' elif nodes < nodes_nl: # increase energy if direction == 'down': delta /= 2 eps += delta direction = 'up' elif nodes == nodes_nl: shift = -0.5 * A / (rgrid[ctp] * norm) if abs(shift) < 1e-8: # convergence break if shift > 0: direction = 'up' elif shift < 0: direction = 'down' eps += shift if eps > epsmax: eps = 0.5 * (epsmax + eps0) hist.append(eps) it += 1 if it > 100: print('Epsilon history for %s' % nl, file=self.txt) for h in hist: print(h) print('nl=%s, eps=%f' % (nl,eps), file=self.txt) print('max epsilon', epsmax, file=self.txt) err = 'Eigensolver out of iterations. Atom not stable?' raise RuntimeError(err) itmax = max(it, itmax) unlg[nl] = u Rnlg[nl] = unlg[nl] / self.rgrid d_enl[nl] = abs(eps - enl[nl]) enl[nl] = eps if self.verbose: line = '-- state %s, %i eigensolver iterations' % (nl, it) line += ', e=%9.5f, de=%9.5f' % (enl[nl], d_enl[nl]) print(line, file=self.txt) assert nodes == nodes_nl assert u[1] > 0.0 self.timer.stop('inner_scf') return itmax, enl, d_enl, unlg, Rnlg
def run(self, rmin=0.4, dr=0.02, N=None, ntheta=150, nr=50, wflimit=1e-7, superposition='potential', xc='LDA', stride=1): """ Calculate the Slater-Koster table. parameters: ------------ rmin, dr, N: parameters defining the equidistant grid of interatomic separations: the shortest distance rmin and grid spacing dr (both in Bohr radii) and the number of grid points N. ntheta: number of angular divisions in polar grid (more dense towards bonding region). nr: number of radial divisions in polar grid (more dense towards origins). with p=q=2 (powers in polar grid) ntheta~3*nr is optimal (with fixed grid size) with ntheta=150, nr=50 you get~1E-4 accuracy for H-elements (beyond that, gain is slow with increasing grid size) wflimit: value below which the radial wave functions are considered to be negligible. This determines how far the polar grids around the atomic centers extend in space. superposition: 'density' or 'potential': whether to use the density superposition or potential superposition approach for the Hamiltonian integrals. xc: name of the exchange-correlation functional to be used in calculating the effective potential in the density superposition scheme. If the PyLibXC module is available, any LDA or GGA (but not hybrid or MGGA) functional available via LibXC can be specified. E.g. for using the N12 functional, set xc='XC_GGA_X_N12+XC_GGA_C_N12'. If PyLibXC is not available, only the local density approximation xc='PW92' (alias: 'LDA') can be chosen. stride: the desired SK-table typically has quite a large number of points (N=500-1000), even though the integrals themselves are comparatively smooth. To speed up the construction of the SK-table, one can therefore restrict the expensive integrations to a subset N' = N // stride, and map the resulting curves on the N-grid afterwards. The default stride = 1 means that N' = N (no shortcut). """ print('\n\n', file=self.txt) print('***********************************************', file=self.txt) print('Slater-Koster table construction for %s and %s' % \ (self.ela.get_symbol(), self.elb.get_symbol()), file=self.txt) print('***********************************************', file=self.txt) self.txt.flush() assert N is not None, 'Need to set number of grid points N!' assert rmin >= 1e-3, 'For stability, please set rmin >= 1e-3' assert superposition in ['density', 'potential'] self.timer.start('calculate_tables') self.wf_range = self.get_range(wflimit) Nsub = N // stride Rgrid = rmin + stride * dr * np.arange(Nsub) tables = [np.zeros((Nsub, 20)) for i in range(self.nel)] dH = 0. Hmax = 0. for p, (e1, e2) in enumerate(self.pairs): print('Integrals:', end=' ', file=self.txt) selected = select_integrals(e1, e2) for s in selected: print(s[0], end=' ', file=self.txt) print(file=self.txt, flush=True) for i, R in enumerate(Rgrid): if R > 2 * self.wf_range: break grid, area = self.make_grid(R, nt=ntheta, nr=nr) if i == Nsub - 1 or Nsub // 10 == 0 or i % (Nsub // 10) == 0: print('R=%8.2f, %i grid points ...' % (R, len(grid)), file=self.txt, flush=True) for p, (e1, e2) in enumerate(self.pairs): selected = select_integrals(e1, e2) S, H, H2 = 0., 0., 0. if len(grid) > 0: S, H, H2 = self.calculate_mels(selected, e1, e2, R, grid, area, xc=xc, superposition=superposition) Hmax = max(Hmax, max(abs(H))) dH = max(dH, max(abs(H - H2))) tables[p][i, :10] = H tables[p][i, 10:] = S if superposition == 'potential': print('Maximum value for H=%.2g' % Hmax, file=self.txt) print('Maximum error for H=%.2g' % dH, file=self.txt) print(' Relative error=%.2g %%' % (dH / Hmax * 100), file=self.txt) self.Rgrid = rmin + dr * np.arange(N) if stride > 1: self.tables = [np.zeros((N, 20)) for i in range(self.nel)] for p in range(self.nel): for i in range(20): spl = CubicSplineFunction(Rgrid, tables[p][:, i]) self.tables[p][:, i] = spl(self.Rgrid) else: self.tables = tables # Smooth the curves near the cutoff for p in range(self.nel): for i in range(20): self.tables[p][:, i] = tail_smoothening(self.Rgrid, self.tables[p][:, i]) self.timer.stop('calculate_tables')
def hartree_potential(self, r): """ Return the Hartree potential at r. """ assert self.solved, not_solved_message if self.vhar_fct is None: self.vhar_fct = CubicSplineFunction(self.rgrid, self.vhar) return self.vhar_fct(r)
def effective_potential(self, r, der=0): """ Return effective potential at r or its derivatives. """ assert self.solved, not_solved_message if self.veff_fct is None: self.veff_fct = CubicSplineFunction(self.rgrid, self.veff) return self.veff_fct(r, der=der)
def electron_density(self, r, der=0): """ Return the all-electron density at r. """ assert self.solved, not_solved_message if self.dens_fct is None: self.dens_fct = CubicSplineFunction(self.rgrid, self.dens) return self.dens_fct(r, der=der)
def unl(self, r, nl, der=0): """ unl(r, '2p') = Rnl(r, '2p') / r """ assert self.solved, not_solved_message if self.unl_fct[nl] is None: self.unl_fct[nl] = CubicSplineFunction(self.rgrid, self.unlg[nl]) return self.unl_fct[nl](r, der=der)