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
Esempio n. 3
0
    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)