def get_ibz_q_points(self, bz_k_points):

        # Get all q-points
        all_qs = []
        for k1 in bz_k_points:
            for k2 in bz_k_points:
                all_qs.append(k1-k2)
        all_qs = np.array(all_qs)

        # Fold q-points into Brillouin zone
        all_qs[np.where(all_qs > 0.501)] -= 1.
        all_qs[np.where(all_qs < -0.499)] += 1.

        # Make list of non-identical q-points in full BZ
        bz_qs = [all_qs[0]]
        for q_a in all_qs:
            q_in_list = False
            for q_b in bz_qs:
                if (abs(q_a[0]-q_b[0]) < 0.01 and
                    abs(q_a[1]-q_b[1]) < 0.01 and
                    abs(q_a[2]-q_b[2]) < 0.01):
                    q_in_list = True
                    break
            if q_in_list == False:
                bz_qs.append(q_a)
        self.bz_q_points = bz_qs
    
        # Obtain q-points and weights in the irreducible part of the BZ
        kpt_descriptor = KPointDescriptor(bz_qs, self.nspins)
        kpt_descriptor.set_symmetry(self.atoms, self.setups, usesymm=True)
        ibz_q_points = kpt_descriptor.ibzk_kc
        q_weights = kpt_descriptor.weight_k
        return ibz_q_points, q_weights
Exemple #2
0
    def create_kpoint_descriptor(self, nspins):
        par = self.parameters

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kpt_refine = par.experimental.get('kpt_refine')
        if kpt_refine is None:
            kd = KPointDescriptor(bzkpts_kc, nspins)

            self.timer.start('Set symmetry')
            kd.set_symmetry(self.atoms, self.symmetry, comm=self.world)
            self.timer.stop('Set symmetry')

        else:
            self.timer.start('Set k-point refinement')
            kd = create_kpoint_descriptor_with_refinement(kpt_refine,
                                                          bzkpts_kc,
                                                          nspins,
                                                          self.atoms,
                                                          self.symmetry,
                                                          comm=self.world,
                                                          timer=self.timer)
            self.timer.stop('Set k-point refinement')
            # Update quantities which might have changed, if symmetry
            # was changed
            self.symmetry = kd.symmetry
            self.setups.set_symmetry(kd.symmetry)

        self.log(kd)

        return kd
Exemple #3
0
    def get_ibz_q_points(self, bz_k_points):

        # Get all q-points
        all_qs = []
        for k1 in bz_k_points:
            for k2 in bz_k_points:
                all_qs.append(k1 - k2)
        all_qs = np.array(all_qs)

        # Fold q-points into Brillouin zone
        all_qs[np.where(all_qs > 0.501)] -= 1.
        all_qs[np.where(all_qs < -0.499)] += 1.

        # Make list of non-identical q-points in full BZ
        bz_qs = [all_qs[0]]
        for q_a in all_qs:
            q_in_list = False
            for q_b in bz_qs:
                if (abs(q_a[0] - q_b[0]) < 0.01 and abs(q_a[1] - q_b[1]) < 0.01
                        and abs(q_a[2] - q_b[2]) < 0.01):
                    q_in_list = True
                    break
            if q_in_list == False:
                bz_qs.append(q_a)
        self.bz_q_points = bz_qs

        # Obtain q-points and weights in the irreducible part of the BZ
        kpt_descriptor = KPointDescriptor(bz_qs, self.nspins)
        kpt_descriptor.set_symmetry(self.atoms, self.setups, usesymm=True)
        ibz_q_points = kpt_descriptor.ibzk_kc
        q_weights = kpt_descriptor.weight_k
        return ibz_q_points, q_weights
Exemple #4
0
    def ibz2bz(self, atoms):
        """Transform wave functions in IBZ to the full BZ."""

        assert self.kd.comm.size == 1

        # New k-point descriptor for full BZ:
        kd = KPointDescriptor(self.kd.bzk_kc, nspins=self.nspins)
        kd.set_symmetry(atoms, self.setups, usesymm=None)
        kd.set_communicator(serial_comm)

        self.pt = LFC(self.gd, [setup.pt_j for setup in self.setups],
                      kd, dtype=self.dtype)
        self.pt.set_positions(atoms.get_scaled_positions())

        self.initialize_wave_functions_from_restart_file()

        weight = 2.0 / kd.nspins / kd.nbzkpts
        
        # Build new list of k-points:
        kpt_u = []
        for s in range(self.nspins):
            for k in range(kd.nbzkpts):
                # Index of symmetry related point in the IBZ
                ik = self.kd.bz2ibz_k[k]
                r, u = self.kd.get_rank_and_index(s, ik)
                assert r == 0
                kpt = self.kpt_u[u]
            
                phase_cd = np.exp(2j * np.pi * self.gd.sdisp_cd *
                                  kd.bzk_kc[k, :, np.newaxis])

                # New k-point:
                kpt2 = KPoint(weight, s, k, k, phase_cd)
                kpt2.f_n = kpt.f_n / kpt.weight / kd.nbzkpts * 2 / self.nspins
                kpt2.eps_n = kpt.eps_n.copy()
                
                # Transform wave functions using symmetry operation:
                Psit_nG = self.gd.collect(kpt.psit_nG)
                if Psit_nG is not None:
                    Psit_nG = Psit_nG.copy()
                    for Psit_G in Psit_nG:
                        Psit_G[:] = self.kd.transform_wave_function(Psit_G, k)
                kpt2.psit_nG = self.gd.empty(self.bd.nbands, dtype=self.dtype)
                self.gd.distribute(Psit_nG, kpt2.psit_nG)

                # Calculate PAW projections:
                kpt2.P_ani = self.pt.dict(len(kpt.psit_nG))
                self.pt.integrate(kpt2.psit_nG, kpt2.P_ani, k)
                
                kpt_u.append(kpt2)

        self.kd = kd
        self.kpt_u = kpt_u
Exemple #5
0
class UTGaussianWavefunctionSetup(UTDomainParallelSetup):
    __doc__ = UTDomainParallelSetup.__doc__ + """
    The pseudo wavefunctions are moving gaussians centered around each atom."""

    allocated = False
    dtype = None

    # Default arguments for scaled Gaussian wave
    _sigma0 = 2.0 #0.75
    _k0_c = 2*np.pi*np.array([1/5., 1/3., 0.])

    def setUp(self):
        UTDomainParallelSetup.setUp(self)

        for virtvar in ['dtype']:
            assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar

        # Create randomized atoms
        self.atoms = create_random_atoms(self.gd, 5) # also tested: 10xNH3/BDA

        # XXX DEBUG START
        if False:
            from ase import view
            view(self.atoms*(1+2*self.gd.pbc_c))
        # XXX DEBUG END

        # Do we agree on the atomic positions?
        pos_ac = self.atoms.get_positions()
        pos_rac = np.empty((world.size,)+pos_ac.shape, pos_ac.dtype)
        world.all_gather(pos_ac, pos_rac)
        if (pos_rac-pos_rac[world.rank,...][np.newaxis,...]).any():
            raise RuntimeError('Discrepancy in atomic positions detected.')

        # Create setups for atoms
        self.Z_a = self.atoms.get_atomic_numbers()
        self.setups = Setups(self.Z_a, p.setups, p.basis,
                             p.lmax, xc)

        # K-point descriptor
        bzk_kc = np.array([[0, 0, 0]], dtype=float)
        self.kd = KPointDescriptor(bzk_kc, 1)
        self.kd.set_symmetry(self.atoms, self.setups, usesymm=True)
        self.kd.set_communicator(self.kpt_comm)
        
        # Create gamma-point dummy wavefunctions
        self.wfs = FDWFS(self.gd, self.bd, self.kd, self.setups,
                         self.dtype)
        
        spos_ac = self.atoms.get_scaled_positions() % 1.0
        self.wfs.set_positions(spos_ac)
        self.pt = self.wfs.pt # XXX shortcut

        ## Also create pseudo partial waveves
        #from gpaw.lfc import LFC
        #self.phit = LFC(self.gd, [setup.phit_j for setup in self.setups], \
        #                self.kpt_comm, dtype=self.dtype)
        #self.phit.set_positions(spos_ac)

        self.r_cG = None
        self.buf_G = None
        self.psit_nG = None

        self.allocate()

    def tearDown(self):
        UTDomainParallelSetup.tearDown(self)
        del self.r_cG, self.buf_G, self.psit_nG
        del self.pt, self.setups, self.atoms
        self.allocated = False

    def allocate(self):
        self.r_cG = self.gd.get_grid_point_coordinates()
        cell_cv = self.atoms.get_cell() / Bohr
        assert np.abs(cell_cv-self.gd.cell_cv).max() < 1e-9
        center_c = 0.5*cell_cv.diagonal()

        self.buf_G = self.gd.empty(dtype=self.dtype)
        self.psit_nG = self.gd.empty(self.bd.mynbands, dtype=self.dtype)

        for myn,psit_G in enumerate(self.psit_nG):
            n = self.bd.global_index(myn)
            psit_G[:] = self.get_scaled_gaussian_wave(center_c, scale=10+2j*n)

            k_c = 2*np.pi*np.array([1/2., -1/7., 0.])
            for pos_c in self.atoms.get_positions() / Bohr:
                sigma = self._sigma0/(1+np.sum(pos_c**2))**0.5
                psit_G += self.get_scaled_gaussian_wave(pos_c, sigma, k_c, n+5j)

        self.allocated = True

    def get_scaled_gaussian_wave(self, pos_c, sigma=None, k_c=None, scale=None):
        if sigma is None:
            sigma = self._sigma0

        if k_c is None:
            k_c = self._k0_c

        if scale is None:
            A = None
        else:
            # 4*pi*int(exp(-r^2/(2*w^2))^2*r^2, r=0...infinity)= w^3*pi^(3/2)
            # = scale/A^2 -> A = scale*(sqrt(Pi)*w)^(-3/2) hence int -> scale^2
            A = scale/(sigma*(np.pi)**0.5)**1.5

        return gaussian_wave(self.r_cG, pos_c, sigma, k_c, A, self.dtype, self.buf_G)

    def check_and_plot(self, P_ani, P0_ani, digits, keywords=''):
        # Collapse into viewable matrices
        P_In = self.wfs.collect_projections(P_ani)
        P0_In = self.wfs.collect_projections(P0_ani)

        # Construct fingerprint of input matrices for comparison
        fingerprint = np.array([md5_array(P_In, numeric=True),
                                md5_array(P0_In, numeric=True)])

        # Compare fingerprints across all processors
        fingerprints = np.empty((world.size, 2), np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        # If assertion fails, catch temporarily while plotting, then re-raise
        try:
            self.assertAlmostEqual(np.abs(P_In-P0_In).max(), 0, digits)
        except AssertionError:
            if world.rank == 0 and mpl is not None:
                from matplotlib.figure import Figure
                fig = Figure()
                ax = fig.add_axes([0.0, 0.1, 1.0, 0.83])
                ax.set_title(self.__class__.__name__)
                im = ax.imshow(np.abs(P_In-P0_In), interpolation='nearest')
                fig.colorbar(im)
                fig.text(0.5, 0.05, 'Keywords: ' + keywords, \
                    horizontalalignment='center', verticalalignment='top')

                from matplotlib.backends.backend_agg import FigureCanvasAgg
                img = 'ut_invops_%s_%s.png' % (self.__class__.__name__, \
                    '_'.join(keywords.split(',')))
                FigureCanvasAgg(fig).print_figure(img.lower(), dpi=90)
            raise

    # =================================

    def test_projection_linearity(self):
        kpt = self.wfs.kpt_u[0]
        Q_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(self.psit_nG, Q_ani, q=kpt.q)

        for Q_ni in Q_ani.values():
            self.assertTrue(Q_ni.dtype == self.dtype)

        P0_ani = dict([(a,Q_ni.copy()) for a,Q_ni in Q_ani.items()])
        self.pt.add(self.psit_nG, Q_ani, q=kpt.q)
        self.pt.integrate(self.psit_nG, P0_ani, q=kpt.q)

        #rank_a = self.gd.get_ranks_from_positions(spos_ac)
        #my_atom_indices = np.argwhere(self.gd.comm.rank == rank_a).ravel()

        #                                                ~ a   ~ a'
        #TODO XXX should fix PairOverlap-ish stuff for < p  | phi  > overlaps
        #                                                 i      i'

        #spos_ac = self.pt.spos_ac # NewLFC doesn't have this
        spos_ac = self.atoms.get_scaled_positions() % 1.0
        gpo = GridPairOverlap(self.gd, self.setups)
        B_aa = gpo.calculate_overlaps(spos_ac, self.pt)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        P_ani = dict([(a,Q_ni.copy()) for a,Q_ni in Q_ani.items()])
        for a1 in range(len(self.atoms)):
            if a1 in P_ani.keys():
                P_ni = P_ani[a1]
            else:
                # Atom a1 is not in domain so allocate a temporary buffer
                P_ni = np.zeros((self.bd.mynbands,self.setups[a1].ni,),
                                 dtype=self.dtype)
            for a2, Q_ni in Q_ani.items():
                # B_aa are the projector overlaps across atomic pairs
                B_ii = gpo.extract_atomic_pair_matrix(B_aa, a1, a2)
                P_ni += np.dot(Q_ni, B_ii.T) #sum over a2 and last i in B_ii
            self.gd.comm.sum(P_ni)

        self.check_and_plot(P_ani, P0_ani, 8, 'projection,linearity')

    def test_extrapolate_overlap(self):
        kpt = self.wfs.kpt_u[0]
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        P_ani = ppo.apply(self.psit_nG, work_nG, self.wfs, kpt, \
            calculate_P_ani=True, extrapolate_P_ani=True)

        P0_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(work_nG, P0_ani, kpt.q)
        del work_nG

        self.check_and_plot(P_ani, P0_ani, 11, 'extrapolate,overlap')

    def test_extrapolate_inverse(self):
        kpt = self.wfs.kpt_u[0]
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        P_ani = ppo.apply_inverse(self.psit_nG, work_nG, self.wfs, kpt, \
            calculate_P_ani=True, extrapolate_P_ani=True)

        P0_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(work_nG, P0_ani, kpt.q)
        del work_nG

        self.check_and_plot(P_ani, P0_ani, 11, 'extrapolate,inverse')

    def test_overlap_inverse_after(self):
        kpt = self.wfs.kpt_u[0]
        kpt.P_ani = self.pt.dict(self.bd.mynbands)
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        self.pt.integrate(self.psit_nG, kpt.P_ani, kpt.q)
        P0_ani = dict([(a,P_ni.copy()) for a,P_ni in kpt.P_ani.items()])
        ppo.apply(self.psit_nG, work_nG, self.wfs, kpt, calculate_P_ani=False)

        res_nG = np.empty_like(self.psit_nG)
        ppo.apply_inverse(work_nG, res_nG, self.wfs, kpt, calculate_P_ani=True)
        del work_nG

        P_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(res_nG, P_ani, kpt.q)

        abserr = np.empty(1, dtype=float)
        for n in range(self.nbands):
            band_rank, myn = self.bd.who_has(n)
            if band_rank == self.bd.comm.rank:
                abserr[:] = np.abs(self.psit_nG[myn] - res_nG[myn]).max()
                self.gd.comm.max(abserr)
            self.bd.comm.broadcast(abserr, band_rank)
            self.assertAlmostEqual(abserr.item(), 0, 10)

        self.check_and_plot(P_ani, P0_ani, 10, 'overlap,inverse,after')

    def test_overlap_inverse_before(self):
        kpt = self.wfs.kpt_u[0]
        kpt.P_ani = self.pt.dict(self.bd.mynbands)
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        self.pt.integrate(self.psit_nG, kpt.P_ani, kpt.q)
        P0_ani = dict([(a,P_ni.copy()) for a,P_ni in kpt.P_ani.items()])
        ppo.apply_inverse(self.psit_nG, work_nG, self.wfs, kpt, calculate_P_ani=False)

        res_nG = np.empty_like(self.psit_nG)
        ppo.apply(work_nG, res_nG, self.wfs, kpt, calculate_P_ani=True)
        del work_nG

        P_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(res_nG, P_ani, kpt.q)

        abserr = np.empty(1, dtype=float)
        for n in range(self.nbands):
            band_rank, myn = self.bd.who_has(n)
            if band_rank == self.bd.comm.rank:
                abserr[:] = np.abs(self.psit_nG[myn] - res_nG[myn]).max()
                self.gd.comm.max(abserr)
            self.bd.comm.broadcast(abserr, band_rank)
            self.assertAlmostEqual(abserr.item(), 0, 10)

        self.check_and_plot(P_ani, P0_ani, 10, 'overlap,inverse,before')
Exemple #6
0
    def create_wave_functions(self, mode, realspace, nspins, nbands, nao,
                              nvalence, setups, magmom_a, cell_cv, pbc_c):
        par = self.parameters

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kd = KPointDescriptor(bzkpts_kc, nspins)

        self.timer.start('Set symmetry')
        kd.set_symmetry(self.atoms, self.symmetry, comm=self.world)
        self.timer.stop('Set symmetry')

        self.log(kd)

        parallelization = mpi.Parallelization(self.world, nspins * kd.nibzkpts)

        parsize_kpt = self.parallel['kpt']
        parsize_domain = self.parallel['domain']
        parsize_bands = self.parallel['band']

        ndomains = None
        if parsize_domain is not None:
            ndomains = np.prod(parsize_domain)
        if mode.name == 'pw':
            if ndomains is not None and ndomains > 1:
                raise ValueError('Planewave mode does not support '
                                 'domain decomposition.')
            ndomains = 1
        parallelization.set(kpt=parsize_kpt,
                            domain=ndomains,
                            band=parsize_bands)
        comms = parallelization.build_communicators()
        domain_comm = comms['d']
        kpt_comm = comms['k']
        band_comm = comms['b']
        kptband_comm = comms['D']
        domainband_comm = comms['K']

        self.comms = comms

        if par.gpts is not None:
            if par.h is not None:
                raise ValueError("""You can't use both "gpts" and "h"!""")
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace,
                                            kd.symmetry)

        self.symmetry.check_grid(N_c)

        kd.set_communicator(kpt_comm)

        parstride_bands = self.parallel['stridebands']

        # Unfortunately we need to remember that we adjusted the
        # number of bands so we can print a warning if it differs
        # from the number specified by the user.  (The number can
        # be inferred from the input parameters, but it's tricky
        # because we allow negative numbers)
        self.nbands_parallelization_adjustment = -nbands % band_comm.size
        nbands += self.nbands_parallelization_adjustment

        bd = BandDescriptor(nbands, band_comm, parstride_bands)

        # Construct grid descriptor for coarse grids for wave functions:
        gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, domain_comm,
                                         parsize_domain)

        if hasattr(self, 'time') or mode.force_complex_dtype:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        wfs_kwargs = dict(gd=gd,
                          nvalence=nvalence,
                          setups=setups,
                          bd=bd,
                          dtype=dtype,
                          world=self.world,
                          kd=kd,
                          kptband_comm=kptband_comm,
                          timer=self.timer)

        if self.parallel['sl_auto']:
            # Choose scalapack parallelization automatically

            for key, val in self.parallel.items():
                if (key.startswith('sl_') and key != 'sl_auto'
                        and val is not None):
                    raise ValueError("Cannot use 'sl_auto' together "
                                     "with '%s'" % key)
            max_scalapack_cpus = bd.comm.size * gd.comm.size
            nprow = max_scalapack_cpus
            npcol = 1

            # Get a sort of reasonable number of columns/rows
            while npcol < nprow and nprow % 2 == 0:
                npcol *= 2
                nprow //= 2
            assert npcol * nprow == max_scalapack_cpus

            # ScaLAPACK creates trouble if there aren't at least a few
            # whole blocks; choose block size so there will always be
            # several blocks.  This will crash for small test systems,
            # but so will ScaLAPACK in any case
            blocksize = min(-(-nbands // 4), 64)
            sl_default = (nprow, npcol, blocksize)
        else:
            sl_default = self.parallel['sl_default']

        if mode.name == 'lcao':
            # Layouts used for general diagonalizer
            sl_lcao = self.parallel['sl_lcao']
            if sl_lcao is None:
                sl_lcao = sl_default
            lcaoksl = get_KohnSham_layouts(sl_lcao,
                                           'lcao',
                                           gd,
                                           bd,
                                           domainband_comm,
                                           dtype,
                                           nao=nao,
                                           timer=self.timer)

            self.wfs = mode(lcaoksl, **wfs_kwargs)

        elif mode.name == 'fd' or mode.name == 'pw':
            # buffer_size keyword only relevant for fdpw
            buffer_size = self.parallel['buffer_size']
            # Layouts used for diagonalizer
            sl_diagonalize = self.parallel['sl_diagonalize']
            if sl_diagonalize is None:
                sl_diagonalize = sl_default
            diagksl = get_KohnSham_layouts(
                sl_diagonalize,
                'fd',  # XXX
                # choice of key 'fd' not so nice
                gd,
                bd,
                domainband_comm,
                dtype,
                buffer_size=buffer_size,
                timer=self.timer)

            # Layouts used for orthonormalizer
            sl_inverse_cholesky = self.parallel['sl_inverse_cholesky']
            if sl_inverse_cholesky is None:
                sl_inverse_cholesky = sl_default
            if sl_inverse_cholesky != sl_diagonalize:
                message = 'sl_inverse_cholesky != sl_diagonalize ' \
                    'is not implemented.'
                raise NotImplementedError(message)
            orthoksl = get_KohnSham_layouts(sl_inverse_cholesky,
                                            'fd',
                                            gd,
                                            bd,
                                            domainband_comm,
                                            dtype,
                                            buffer_size=buffer_size,
                                            timer=self.timer)

            # Use (at most) all available LCAO for initialization
            lcaonbands = min(nbands, nao // band_comm.size * band_comm.size)

            try:
                lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands)
            except RuntimeError:
                initksl = None
            else:
                # Layouts used for general diagonalizer
                # (LCAO initialization)
                sl_lcao = self.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                initksl = get_KohnSham_layouts(sl_lcao,
                                               'lcao',
                                               gd,
                                               lcaobd,
                                               domainband_comm,
                                               dtype,
                                               nao=nao,
                                               timer=self.timer)

            self.wfs = mode(diagksl, orthoksl, initksl, **wfs_kwargs)
        else:
            self.wfs = mode(self, **wfs_kwargs)

        self.log(self.wfs, '\n')
class PhononCalculator:
    """This class defines the interface for phonon calculations."""
    def __init__(self,
                 calc,
                 gamma=True,
                 symmetry=False,
                 e_ph=False,
                 communicator=serial_comm):
        """Inititialize class with a list of atoms.

        The atoms object must contain a converged ground-state calculation.

        The set of q-vectors in which the dynamical matrix will be calculated
        is determined from the ``symmetry`` kwarg. For now, only time-reversal
        symmetry is used to generate the irrecducible BZ.

        Add a little note on parallelization strategy here.

        Parameters
        ----------
        calc: str or Calculator
            Calculator containing a ground-state calculation.
        gamma: bool
            Gamma-point calculation with respect to the q-vector of the
            dynamical matrix. When ``False``, the Monkhorst-Pack grid from the
            ground-state calculation is used.
        symmetry: bool
            Use symmetries to reduce the q-vectors of the dynamcial matrix
            (None, False or True). The different options are equivalent to the
            old style options in a ground-state calculation (see usesymm).
        e_ph: bool
            Save the derivative of the effective potential.
        communicator: Communicator
            Communicator for parallelization over k-points and real-space
            domain.
        """

        # XXX
        assert symmetry in [None, False], "Spatial symmetries not allowed yet"

        if isinstance(calc, str):
            self.calc = GPAW(calc, communicator=serial_comm, txt=None)
        else:
            self.calc = calc

        cell_cv = self.calc.atoms.get_cell()
        setups = self.calc.wfs.setups
        # XXX - no clue how to get magmom - ignore it for the moment
        # m_av = magmom_av.round(decimals=3)  # round off
        # id_a = zip(setups.id_a, *m_av.T)
        id_a = setups.id_a

        if symmetry is None:
            self.symmetry = Symmetry(id_a,
                                     cell_cv,
                                     point_group=False,
                                     time_reversal=False)
        else:
            self.symmetry = Symmetry(id_a,
                                     cell_cv,
                                     point_group=False,
                                     time_reversal=True)

        # Make sure localized functions are initialized
        self.calc.set_positions()
        # Note that this under some circumstances (e.g. when called twice)
        # allocates a new array for the P_ani coefficients !!

        # Store useful objects
        self.atoms = self.calc.get_atoms()
        # Get rid of ``calc`` attribute
        self.atoms.calc = None

        # Boundary conditions
        pbc_c = self.calc.atoms.get_pbc()

        if np.all(pbc_c == False):
            self.gamma = True
            self.dtype = float
            kpts = None
            # Multigrid Poisson solver
            poisson_solver = PoissonSolver()
        else:
            if gamma:
                self.gamma = True
                self.dtype = float
                kpts = None
            else:
                self.gamma = False
                self.dtype = complex
                # Get k-points from ground-state calculation
                kpts = self.calc.input_parameters.kpts

            # FFT Poisson solver
            poisson_solver = FFTPoissonSolver(dtype=self.dtype)

        # K-point descriptor for the q-vectors of the dynamical matrix
        # Note, no explicit parallelization here.
        self.kd = KPointDescriptor(kpts, 1)
        self.kd.set_symmetry(self.atoms, self.symmetry)
        self.kd.set_communicator(serial_comm)

        # Number of occupied bands
        nvalence = self.calc.wfs.nvalence
        nbands = nvalence // 2 + nvalence % 2
        assert nbands <= self.calc.wfs.bd.nbands

        # Extract other useful objects
        # Ground-state k-point descriptor - used for the k-points in the
        # ResponseCalculator
        # XXX replace communicators when ready to parallelize
        kd_gs = self.calc.wfs.kd
        gd = self.calc.density.gd
        kpt_u = self.calc.wfs.kpt_u
        setups = self.calc.wfs.setups
        dtype_gs = self.calc.wfs.dtype

        # WaveFunctions
        wfs = WaveFunctions(nbands, kpt_u, setups, kd_gs, gd, dtype=dtype_gs)

        # Linear response calculator
        self.response_calc = ResponseCalculator(self.calc,
                                                wfs,
                                                dtype=self.dtype)

        # Phonon perturbation
        self.perturbation = PhononPerturbation(self.calc,
                                               self.kd,
                                               poisson_solver,
                                               dtype=self.dtype)

        # Dynamical matrix
        self.dyn = DynamicalMatrix(self.atoms, self.kd, dtype=self.dtype)

        # Electron-phonon couplings
        if e_ph:
            self.e_ph = ElectronPhononCoupling(self.atoms,
                                               gd,
                                               self.kd,
                                               dtype=self.dtype)
        else:
            self.e_ph = None

        # Initialization flag
        self.initialized = False

        # Parallel communicator for parallelization over kpts and domain
        self.comm = communicator

    def initialize(self):
        """Initialize response calculator and perturbation."""

        # Get scaled atomic positions
        spos_ac = self.atoms.get_scaled_positions()

        self.perturbation.initialize(spos_ac)
        self.response_calc.initialize(spos_ac)

        self.initialized = True

    def __getstate__(self):
        """Method used when pickling.

        Bound method attributes cannot be pickled and must therefore be deleted
        before an instance is dumped to file.

        """

        # Get state of object and take care of troublesome attributes
        state = dict(self.__dict__)
        state['kd'].__dict__['comm'] = serial_comm
        state.pop('calc')
        state.pop('perturbation')
        state.pop('response_calc')

        return state

    def run(self, qpts_q=None, clean=False, name=None, path=None):
        """Run calculation for atomic displacements and update matrix.

        Parameters
        ----------
        qpts: List
            List of q-points indices for which the dynamical matrix will be
            calculated (only temporary).

        """

        if not self.initialized:
            self.initialize()

        if self.gamma:
            qpts_q = [0]
        elif qpts_q is None:
            qpts_q = range(self.kd.nibzkpts)
        else:
            assert isinstance(qpts_q, list)

        # Update name and path attributes
        self.set_name_and_path(name=name, path=path)
        # Get string template for filenames
        filename_str = self.get_filename_string()

        # Delay the ranks belonging to the same k-point/domain decomposition
        # equally
        time.sleep(rank // self.comm.size)

        # XXX Make a single ground_state_contributions member function
        # Ground-state contributions to the force constants
        self.dyn.density_ground_state(self.calc)
        # self.dyn.wfs_ground_state(self.calc, self.response_calc)

        # Calculate linear response wrt q-vectors and displacements of atoms
        for q in qpts_q:

            if not self.gamma:
                self.perturbation.set_q(q)

            # First-order contributions to the force constants
            for a in self.dyn.indices:
                for v in [0, 1, 2]:

                    # Check if the calculation has already been done
                    filename = filename_str % (q, a, v)
                    # Wait for all sub-ranks to enter
                    self.comm.barrier()

                    if os.path.isfile(os.path.join(self.path, filename)):
                        continue

                    if self.comm.rank == 0:
                        fd = open(os.path.join(self.path, filename), 'w')

                    # Wait for all sub-ranks here
                    self.comm.barrier()

                    components = ['x', 'y', 'z']
                    symbols = self.atoms.get_chemical_symbols()
                    print("q-vector index: %i" % q)
                    print("Atom index: %i" % a)
                    print("Atomic symbol: %s" % symbols[a])
                    print("Component: %s" % components[v])

                    # Set atom and cartesian component of perturbation
                    self.perturbation.set_av(a, v)
                    # Calculate linear response
                    self.response_calc(self.perturbation)

                    # Calculate row of the matrix of force constants
                    self.dyn.calculate_row(self.perturbation,
                                           self.response_calc)

                    # Write force constants to file
                    if self.comm.rank == 0:
                        self.dyn.write(fd, q, a, v)
                        fd.close()

                    # Store effective potential derivative
                    if self.e_ph is not None:
                        v1_eff_G = self.perturbation.v1_G + \
                            self.response_calc.vHXC1_G
                        self.e_ph.v1_eff_qavG.append(v1_eff_G)

                    # Wait for the file-writing rank here
                    self.comm.barrier()

        # XXX
        # Check that all files are valid and collect in a single file
        # Remove the files
        if clean:
            self.clean()

    def get_atoms(self):
        """Return atoms."""

        return self.atoms

    def get_dynamical_matrix(self):
        """Return reference to ``dyn`` attribute."""

        return self.dyn

    def get_filename_string(self):
        """Return string template for force constant filenames."""

        name_str = (self.name + '.' +
                    'q_%%0%ii_' % len(str(self.kd.nibzkpts)) +
                    'a_%%0%ii_' % len(str(len(self.atoms))) + 'v_%i' + '.pckl')

        return name_str

    def set_atoms(self, atoms):
        """Set atoms to be included in the calculation.

        Parameters
        ----------
        atoms: list
            Can be either a list of strings, ints or ...
        """

        assert isinstance(atoms, list)

        if isinstance(atoms[0], str):
            assert np.all([isinstance(atom, str) for atom in atoms])
            sym_a = self.atoms.get_chemical_symbols()
            # List for atomic indices
            indices = []
            for type in atoms:
                indices.extend(
                    [a for a, atom in enumerate(sym_a) if atom == type])
        else:
            assert np.all([isinstance(atom, int) for atom in atoms])
            indices = atoms

        self.dyn.set_indices(indices)

    def set_name_and_path(self, name=None, path=None):
        """Set name and path of the force constant files.

        name: str
            Base name for the files which the elements of the matrix of force
            constants will be written to.
        path: str
            Path specifying the directory where the files will be dumped.
        """

        if name is None:
            self.name = 'phonon.' + self.atoms.get_chemical_formula()
        else:
            self.name = name
        # self.name += '.nibzkpts_%i' % self.kd.nibzkpts

        if path is None:
            self.path = '.'
        else:
            self.path = path

        # Set corresponding attributes in the ``dyn`` attribute
        filename_str = self.get_filename_string()
        self.dyn.set_name_and_path(filename_str, self.path)

    def clean(self):
        """Delete generated files."""

        filename_str = self.get_filename_string()

        for q in range(self.kd.nibzkpts):
            for a in range(len(self.atoms)):
                for v in [0, 1, 2]:
                    filename = filename_str % (q, a, v)
                    if os.path.isfile(os.path.join(self.path, filename)):
                        os.remove(filename)

    def band_structure(self, path_kc, modes=False, acoustic=True):
        """Calculate phonon dispersion along a path in the Brillouin zone.

        The dynamical matrix at arbitrary q-vectors is obtained by Fourier
        transforming the real-space matrix. In case of negative eigenvalues
        (squared frequency), the corresponding negative frequency is returned.

        Parameters
        ----------
        path_kc: ndarray
            List of k-point coordinates (in units of the reciprocal lattice
            vectors) specifying the path in the Brillouin zone for which the
            dynamical matrix will be calculated.
        modes: bool
            Returns both frequencies and modes (mass scaled) when True.
        acoustic: bool
            Restore the acoustic sum-rule in the calculated force constants.
        """

        for k_c in path_kc:
            assert np.all(np.asarray(k_c) <= 1.0), \
                "Scaled coordinates must be given"

        # Assemble the dynanical matrix from calculated force constants
        self.dyn.assemble(acoustic=acoustic)
        # Get the dynamical matrix in real-space
        DR_lmn, R_clmn = self.dyn.real_space()

        # Reshape for the evaluation of the fourier sums
        shape = DR_lmn.shape
        DR_m = DR_lmn.reshape((-1, ) + shape[-2:])
        R_cm = R_clmn.reshape((3, -1))

        # Lists for frequencies and modes along path
        omega_kn = []
        u_kn = []
        # Number of atoms included
        N = len(self.dyn.get_indices())

        # Mass prefactor for the normal modes
        m_inv_av = self.dyn.get_mass_array()

        for q_c in path_kc:

            # Evaluate fourier transform
            phase_m = np.exp(-2.j * pi * np.dot(q_c, R_cm))
            # Dynamical matrix in unit of Ha / Bohr**2 / amu
            D_q = np.sum(phase_m[:, np.newaxis, np.newaxis] * DR_m, axis=0)

            if modes:
                omega2_n, u_avn = la.eigh(D_q, UPLO='L')
                # Sort eigenmodes according to eigenvalues (see below) and
                # multiply with mass prefactor
                u_nav = u_avn[:, omega2_n.argsort()].T.copy() * m_inv_av
                # Multiply with mass prefactor
                u_kn.append(u_nav.reshape((3 * N, -1, 3)))
            else:
                omega2_n = la.eigvalsh(D_q, UPLO='L')

            # Sort eigenvalues in increasing order
            omega2_n.sort()
            # Use dtype=complex to handle negative eigenvalues
            omega_n = np.sqrt(omega2_n.astype(complex))

            # Take care of imaginary frequencies
            if not np.all(omega2_n >= 0.):
                indices = np.where(omega2_n < 0)[0]
                print(("WARNING, %i imaginary frequencies at "
                       "q = (% 5.2f, % 5.2f, % 5.2f) ; (omega_q =% 5.3e*i)" %
                       (len(indices), q_c[0], q_c[1], q_c[2],
                        omega_n[indices][0].imag)))

                omega_n[indices] = -1 * np.sqrt(np.abs(omega2_n[indices].real))

            omega_kn.append(omega_n.real)

        # Conversion factor from sqrt(Ha / Bohr**2 / amu) -> eV
        s = units.Hartree**0.5 * units._hbar * 1.e10 / \
            (units._e * units._amu)**(0.5) / units.Bohr
        # Convert to eV and Ang
        omega_kn = s * np.asarray(omega_kn)
        if modes:
            u_kn = np.asarray(u_kn) * units.Bohr
            return omega_kn, u_kn

        return omega_kn

    def write_modes(self,
                    q_c,
                    branches=0,
                    kT=units.kB * 300,
                    repeat=(1, 1, 1),
                    nimages=30,
                    acoustic=True):
        """Write mode to trajectory file.

        The classical equipartioning theorem states that each normal mode has
        an average energy::

            <E> = 1/2 * k_B * T = 1/2 * omega^2 * Q^2

                =>

              Q = sqrt(k_B*T) / omega

        at temperature T. Here, Q denotes the normal coordinate of the mode.

        Parameters
        ----------
        q_c: ndarray
            q-vector of the modes.
        branches: int or list
            Branch index of calculated modes.
        kT: float
            Temperature in units of eV. Determines the amplitude of the atomic
            displacements in the modes.
        repeat: tuple
            Repeat atoms (l, m, n) times in the directions of the lattice
            vectors. Displacements of atoms in repeated cells carry a Bloch
            phase factor given by the q-vector and the cell lattice vector R_m.
        nimages: int
            Number of images in an oscillation.

        """

        if isinstance(branches, int):
            branch_n = [branches]
        else:
            branch_n = list(branches)

        # Calculate modes
        omega_n, u_n = self.band_structure([q_c],
                                           modes=True,
                                           acoustic=acoustic)

        # Repeat atoms
        atoms = self.atoms * repeat
        pos_mav = atoms.positions.copy()
        # Total number of unit cells
        M = np.prod(repeat)

        # Corresponding lattice vectors R_m
        R_cm = np.indices(repeat[::-1]).reshape(3, -1)[::-1]
        # Bloch phase
        phase_m = np.exp(2.j * pi * np.dot(q_c, R_cm))
        phase_ma = phase_m.repeat(len(self.atoms))

        for n in branch_n:
            omega = omega_n[0, n]
            u_av = u_n[0, n]  # .reshape((-1, 3))
            # Mean displacement at high T ?
            u_av *= sqrt(kT / abs(omega))

            mode_av = np.zeros((len(self.atoms), 3), dtype=self.dtype)
            indices = self.dyn.get_indices()
            mode_av[indices] = u_av
            mode_mav = (np.vstack([mode_av] * M) *
                        phase_ma[:, np.newaxis]).real

            traj = Trajectory('%s.mode.%d.traj' % (self.name, n), 'w')

            for x in np.linspace(0, 2 * pi, nimages, endpoint=False):
                # XXX Is it correct to take out the sine component here ?
                atoms.set_positions(pos_mav + sin(x) * mode_mav)
                traj.write(atoms)

            traj.close()
Exemple #8
0
def create_kpoint_descriptor(bzkpts_kc, nspins, atoms, symmetry, comm):
    kd = KPointDescriptor(bzkpts_kc, nspins)
    # self.timer.start('Set symmetry')
    kd.set_symmetry(atoms, symmetry, comm=comm)
    # self.timer.stop('Set symmetry')
    return kd
Exemple #9
0
class Phonons(phonons.Phonons):
    """DFPT version of the ``Phonons`` class from ase.

    This class recycle the functionality from the ASE class.

    """

    def __init__(self, atoms, kpts, symmetry):
        """Initialize base class and attributes.

        Parameters
        ----------
        atoms: Atoms
            ASE atoms.
        kpts: tuple or list of tuples
            Shape of Monkhorst-Pack grid or list of k-points used in the dfpt
            calculation.
        symmetry: bool or None
            Symmetry parameter used in dfpt calculation.
            
        """

        # Init base class with ``Atoms`` object
        phonons.Phonons.__init__(atoms)

        # Create k-point descriptor
        self.kd = KPointDescriptor(kpts, 1)
        self.kd.set_symmetry(self.atoms, self.calc.wfs.setups, symmetry)

        # Overwrite ``N_c`` attribute
        self.N_c = tuple(self.kd.N_c)

        # Index of the gamma point -- for the acoustic sum-rule
        self.gamma_index = None
        if self.kd.gamma:
            self.gamma_index = 0
            self.dtype == float
        else:
            self.dtype == comples
            for k, k_c in enumerate(self.kd.ibzk_kc):
                if np.all(k_c == 0.):
                    self.gamma_index = k

        assert self.gamma_index is not None
        
    def run(self):
        """Overwrite base class member function."""

        raise RuntimeError, "Use only this class for post-processing"
    
    def read(self):
        """Read force constants from files."""

        # Data structure for force constants
        self.C_qaavv = [dict([(a, dict([(a_, np.zeros((3, 3), dtype=dtype))
                                        for a_ in self.indices]))
                              for a in self.indices])
                        for q in range(self.kd.nibzkpts)]
        
        assert self.name is not None
        assert self.path is not None
        
        for q in range(self.kd.nibzkpts):
            for a in self.indices:
                for v in [0, 1, 2]:
                    filename = self.name % (q, a, v)
                    try:
                        fd = open(os.path.join(self.path, filename))
                    except EOFError:
                        print ("Redo file %s "
                               % os.path.join(self.path, filename))
                    C_qav_a = pickle.load(fd)
                    fd.close()
                    for a_ in self.indices:
                        self.C_qaavv[q][a][a_][v] = C_qav_a[a_]

    def assemble(self, acoustic=True):
        """Assemble dynamical matrix from the force constants attribute.

        The elements of the dynamical matrix are given by::

            D_ij(q) = 1/(M_i + M_j) * C_ij(q) ,
                      
        where i and j are collective atomic and cartesian indices.

        During the assembly, various symmetries of the dynamical matrix are
        enforced::

            1) Hermiticity
            2) Acoustic sum-rule
            3) D(q) = D*(-q)

        Parameters
        ----------
        acoustic: bool
            When True, the diagonal of the matrix of force constants is
            corrected to ensure that the acoustic sum-rule is fulfilled.
            
        """

        # Read force constants from files
        self.read()

        # Number of atoms included
        N = len(self.indices)
        
        # Assemble matrix of force constants
        self.C_q = []
        for q, C_aavv in enumerate(self.C_qaavv):

            C_avav = np.zeros((3*N, 3*N), dtype=self.dtype)
    
            for i, a in enumerate(self.indices):
                for j, a_ in enumerate(self.indices):
                    C_avav[3*i : 3*i + 3, 3*j : 3*j + 3] += C_aavv[a][a_]

            self.C_q.append(C_avav)

        # XXX Figure out in which order the corrections should be done
        # Make C(q) Hermitian
        for C in self.C_q:
            C *= 0.5
            C += C.T.conj()

        # Get matrix of force constants in the Gamma-point (is real!)
        C_gamma = self.C_q[self.gamma_index].real
        # Make Gamma-component real
        self.C_q[self.gamma_index] = C_gamma.copy()
            
        # Apply acoustic sum-rule if requested
        if acoustic:
            # Correct atomic diagonal for each q-vector
            for C in self.C_q:
                for a in range(N):
                    for a_ in range(N):
                        C[3*a : 3*a + 3, 3*a : 3*a + 3] -= \
                              C_gamma[3*a: 3*a+3, 3*a_: 3*a_+3]

            # Check sum-rule for Gamma-component in debug mode
            if debug:
                C = self.C_q[self.gamma_index]
                assert np.all(np.sum(C.reshape((3*N, N, 3)), axis=1) < 1e-15)

        
        # Move this bit to an ``unfold`` member function
        # XXX Time-reversal symmetry
        C_q = np.asarray(self.C_q)
        if self.kd.nibzkpts != self.kd.nbzkpts:
            self.D_k = np.concatenate((C_q[:0:-1].conjugate(), C_q))
        else:
            self.D_k = 0.5 * C_q
            self.D_k += self.D_k[::-1].conjugate()
            
        # Mass prefactor for the dynamical matrix
        m = self.atoms.get_masses()
        self.m_inv_av = np.repeat(m[self.indices]**-0.5, 3)
        M_avav = self.m_inv_av[:, np.newaxis] * self.m_inv_av

        for C in self.D_k:
            C *= M_avav

        self.assembled = True
       
    def real_space(self):
        """Fourier transform the dynamical matrix to real-space."""

        if not self.assembled:
            self.assemble()

        # Shape of q-point grid
        N_c = self.N_c

        # Reshape before Fourier transforming
        shape = self.D_k.shape
        Dq_lmn = self.D_k.reshape(N_c + shape[1:])
        DR_lmn = fft.ifftn(fft.ifftshift(Dq_lmn, axes=(0, 1, 2)), axes=(0, 1, 2))

        if debug:
            # Check that D_R is real enough
            assert np.all(DR_lmn.imag < 1e-8)
            
        DR_lmn = DR_lmn.real

        # Corresponding R_m vectors in units of the basis vectors
        R_cm = np.indices(N_c).reshape(3, -1)
        N1_c = np.array(N_c)[:, np.newaxis]        
        R_cm += N1_c // 2
        R_cm %= N1_c
        R_cm -= N1_c // 2
        R_clmn = R_cm.reshape((3,) + N_c)

        return DR_lmn, R_clmn
    
    def fourier_interpolate(self, N_c):
        """Fourier interpolate dynamical matrix onto a finer q-vector grid."""

        # Move this member function to the ASE class
        raise NotImplementedError    
Exemple #10
0
class PhononCalculator:
    """This class defines the interface for phonon calculations."""
    
    def __init__(self, calc, gamma=True, symmetry=False, e_ph=False,
                 communicator=serial_comm):
        """Inititialize class with a list of atoms.

        The atoms object must contain a converged ground-state calculation.

        The set of q-vectors in which the dynamical matrix will be calculated
        is determined from the ``symmetry`` kwarg. For now, only time-reversal
        symmetry is used to generate the irrecducible BZ.

        Add a little note on parallelization strategy here.

        Parameters
        ----------
        calc: str or Calculator
            Calculator containing a ground-state calculation.
        gamma: bool
            Gamma-point calculation with respect to the q-vector of the
            dynamical matrix. When ``False``, the Monkhorst-Pack grid from the
            ground-state calculation is used.
        symmetry: bool
            Use symmetries to reduce the q-vectors of the dynamcial matrix
            (None, False or True). The different options are equivalent to the
            options in a ground-state calculation.
        e_ph: bool
            Save the derivative of the effective potential.
        communicator: Communicator
            Communicator for parallelization over k-points and real-space
            domain.
            
        """

        # XXX
        assert symmetry in [None, False], "Spatial symmetries not allowed yet"

        self.symmetry = symmetry

        if isinstance(calc, str):
            self.calc = GPAW(calc, communicator=serial_comm, txt=None)
        else:
            self.calc = calc

        # Make sure localized functions are initialized
        self.calc.set_positions()
        # Note that this under some circumstances (e.g. when called twice)
        # allocates a new array for the P_ani coefficients !!

        # Store useful objects
        self.atoms = self.calc.get_atoms()
        # Get rid of ``calc`` attribute
        self.atoms.calc = None
 
        # Boundary conditions
        pbc_c = self.calc.atoms.get_pbc()

        if np.all(pbc_c == False):
            self.gamma = True
            self.dtype = float
            kpts = None
            # Multigrid Poisson solver
            poisson_solver = PoissonSolver()
        else:
            if gamma:
                self.gamma = True
                self.dtype = float
                kpts = None
            else:
                self.gamma = False
                self.dtype = complex
                # Get k-points from ground-state calculation
                kpts = self.calc.input_parameters.kpts
                
            # FFT Poisson solver
            poisson_solver = FFTPoissonSolver(dtype=self.dtype)

        # K-point descriptor for the q-vectors of the dynamical matrix
        # Note, no explicit parallelization here.
        self.kd = KPointDescriptor(kpts, 1)
        self.kd.set_symmetry(self.atoms, self.calc.wfs.setups,
                             usesymm=symmetry)
        self.kd.set_communicator(serial_comm)

        # Number of occupied bands
        nvalence = self.calc.wfs.nvalence
        nbands = nvalence / 2 + nvalence % 2
        assert nbands <= self.calc.wfs.bd.nbands

        # Extract other useful objects
        # Ground-state k-point descriptor - used for the k-points in the
        # ResponseCalculator
        # XXX replace communicators when ready to parallelize
        kd_gs = self.calc.wfs.kd
        gd = self.calc.density.gd
        kpt_u = self.calc.wfs.kpt_u
        setups = self.calc.wfs.setups
        dtype_gs = self.calc.wfs.dtype
        
        # WaveFunctions
        wfs = WaveFunctions(nbands, kpt_u, setups, kd_gs, gd, dtype=dtype_gs)

        # Linear response calculator
        self.response_calc = ResponseCalculator(self.calc, wfs,
                                                dtype=self.dtype)
        
        # Phonon perturbation
        self.perturbation = PhononPerturbation(self.calc, self.kd,
                                               poisson_solver,
                                               dtype=self.dtype)

        # Dynamical matrix
        self.dyn = DynamicalMatrix(self.atoms, self.kd, dtype=self.dtype)

        # Electron-phonon couplings
        if e_ph:
            self.e_ph = ElectronPhononCoupling(self.atoms, gd, self.kd,
                                               dtype=self.dtype)
        else:
            self.e_ph = None
                                               
        # Initialization flag
        self.initialized = False

        # Parallel communicator for parallelization over kpts and domain
        self.comm = communicator

    def initialize(self):
        """Initialize response calculator and perturbation."""

        # Get scaled atomic positions
        spos_ac = self.atoms.get_scaled_positions()

        self.perturbation.initialize(spos_ac)
        self.response_calc.initialize(spos_ac)

        self.initialized = True
        
    def __getstate__(self): 
        """Method used when pickling.

        Bound method attributes cannot be pickled and must therefore be deleted
        before an instance is dumped to file.

        """

        # Get state of object and take care of troublesome attributes
        state = dict(self.__dict__)
        state['kd'].__dict__['comm'] = serial_comm
        state.pop('calc')
        state.pop('perturbation')
        state.pop('response_calc')
        
        return state

    def run(self, qpts_q=None, clean=False, name=None, path=None):
        """Run calculation for atomic displacements and update matrix.

        Parameters
        ----------
        qpts: List
            List of q-points indices for which the dynamical matrix will be
            calculated (only temporary).

        """

        if not self.initialized:
            self.initialize()

        if self.gamma:
            qpts_q = [0]
        elif qpts_q is None:
            qpts_q = range(self.kd.nibzkpts)
        else:
            assert isinstance(qpts_q, list)

        # Update name and path attributes
        self.set_name_and_path(name=name, path=path)
        # Get string template for filenames
        filename_str = self.get_filename_string()

        # Delay the ranks belonging to the same k-point/domain decomposition
        # equally
        time.sleep(rank // self.comm.size)

        # XXX Make a single ground_state_contributions member function
        # Ground-state contributions to the force constants
        self.dyn.density_ground_state(self.calc)
        # self.dyn.wfs_ground_state(self.calc, self.response_calc)
        
        # Calculate linear response wrt q-vectors and displacements of atoms
        for q in qpts_q:
            
            if not self.gamma:
                self.perturbation.set_q(q)

            # First-order contributions to the force constants
            for a in self.dyn.indices:
                for v in [0, 1, 2]:

                    # Check if the calculation has already been done
                    filename = filename_str % (q, a, v)
                    # Wait for all sub-ranks to enter
                    self.comm.barrier()
                    
                    if os.path.isfile(os.path.join(self.path, filename)):
                        continue

                    if self.comm.rank == 0:
                        fd = open(os.path.join(self.path, filename), 'w')

                    # Wait for all sub-ranks here
                    self.comm.barrier()
                    
                    components = ['x', 'y', 'z']
                    symbols = self.atoms.get_chemical_symbols()
                    print "q-vector index: %i" % q
                    print "Atom index: %i" % a
                    print "Atomic symbol: %s" % symbols[a]
                    print "Component: %s" % components[v]

                    # Set atom and cartesian component of perturbation
                    self.perturbation.set_av(a, v)
                    # Calculate linear response
                    self.response_calc(self.perturbation)

                    # Calculate row of the matrix of force constants
                    self.dyn.calculate_row(self.perturbation,
                                           self.response_calc)

                    # Write force constants to file
                    if self.comm.rank == 0:
                        self.dyn.write(fd, q, a, v)
                        fd.close()
                        
                    # Store effective potential derivative
                    if self.e_ph is not None:
                        v1_eff_G = self.perturbation.v1_G + \
                                   self.response_calc.vHXC1_G
                        self.e_ph.v1_eff_qavG.append(v1_eff_G)

                    # Wait for the file-writing rank here
                    self.comm.barrier()

        # XXX
        # Check that all files are valid and collect in a single file
        # Remove the files
        if clean:
            self.clean()
            
    def get_atoms(self):
        """Return atoms."""

        return self.atoms
    
    def get_dynamical_matrix(self):
        """Return reference to ``dyn`` attribute."""
        
        return self.dyn

    def get_filename_string(self):
        """Return string template for force constant filenames."""

        name_str = (self.name + '.' + 'q_%%0%ii_' % len(str(self.kd.nibzkpts)) +
                    'a_%%0%ii_' % len(str(len(self.atoms))) + 'v_%i' + '.pckl')

        return name_str
    
    def set_atoms(self, atoms):
        """Set atoms to be included in the calculation.

        Parameters
        ----------
        atoms: list
            Can be either a list of strings, ints or ...
            
        """
        
        assert isinstance(atoms, list)
        
        if isinstance(atoms[0], str):
            assert np.all([isinstance(atom, str) for atom in atoms])
            sym_a = self.atoms.get_chemical_symbols()
            # List for atomic indices
            indices = []
            for type in atoms:
                indices.extend([a for a, atom in enumerate(sym_a)
                                if atom == type])
        else:
            assert np.all([isinstance(atom, int) for atom in atoms])
            indices = atoms
            
        self.dyn.set_indices(indices)

    def set_name_and_path(self, name=None, path=None):
        """Set name and path of the force constant files.

        name: str
            Base name for the files which the elements of the matrix of force
            constants will be written to.
        path: str
            Path specifying the directory where the files will be dumped.
            
        """

        if name is None:
            self.name = 'phonon.' + self.atoms.get_chemical_formula()
        else:
            self.name = name
        # self.name += '.nibzkpts_%i' % self.kd.nibzkpts
        
        if path is None:
            self.path = '.'
        else:
            self.path = path

        # Set corresponding attributes in the ``dyn`` attribute
        filename_str = self.get_filename_string()
        self.dyn.set_name_and_path(filename_str, self.path)
            
    def clean(self):
        """Delete generated files."""

        filename_str = self.get_filename_string()
        
        for q in range(self.kd.nibzkpts):
            for a in range(len(self.atoms)):
                for v in [0, 1, 2]:
                    filename = filename_str % (q, a, v)
                    if os.path.isfile(os.path.join(self.path, filename)):
                        os.remove(filename)
                        
    def band_structure(self, path_kc, modes=False, acoustic=True):
        """Calculate phonon dispersion along a path in the Brillouin zone.

        The dynamical matrix at arbitrary q-vectors is obtained by Fourier
        transforming the real-space matrix. In case of negative eigenvalues
        (squared frequency), the corresponding negative frequency is returned.

        Parameters
        ----------
        path_kc: ndarray
            List of k-point coordinates (in units of the reciprocal lattice
            vectors) specifying the path in the Brillouin zone for which the
            dynamical matrix will be calculated.
        modes: bool
            Returns both frequencies and modes (mass scaled) when True.
        acoustic: bool
            Restore the acoustic sum-rule in the calculated force constants.
            
        """

        for k_c in path_kc:
            assert np.all(np.asarray(k_c) <= 1.0), \
                   "Scaled coordinates must be given"

        # Assemble the dynanical matrix from calculated force constants
        self.dyn.assemble(acoustic=acoustic)
        # Get the dynamical matrix in real-space
        DR_lmn, R_clmn = self.dyn.real_space()

        # Reshape for the evaluation of the fourier sums
        shape = DR_lmn.shape
        DR_m = DR_lmn.reshape((-1,) + shape[-2:])
        R_cm = R_clmn.reshape((3, -1))

        # Lists for frequencies and modes along path
        omega_kn = []
        u_kn =  []
        # Number of atoms included
        N = len(self.dyn.get_indices())
        
        # Mass prefactor for the normal modes
        m_inv_av = self.dyn.get_mass_array()
        
        for q_c in path_kc:

            # Evaluate fourier transform 
            phase_m = np.exp(-2.j * pi * np.dot(q_c, R_cm))
            # Dynamical matrix in unit of Ha / Bohr**2 / amu
            D_q = np.sum(phase_m[:, np.newaxis, np.newaxis] * DR_m, axis=0)

            if modes:
                omega2_n, u_avn = la.eigh(D_q, UPLO='L')
                # Sort eigenmodes according to eigenvalues (see below) and
                # multiply with mass prefactor
                u_nav = u_avn[:, omega2_n.argsort()].T.copy() * m_inv_av
                # Multiply with mass prefactor
                u_kn.append(u_nav.reshape((3*N, -1, 3)))
            else:
                omega2_n = la.eigvalsh(D_q, UPLO='L')

            # Sort eigenvalues in increasing order
            omega2_n.sort()
            # Use dtype=complex to handle negative eigenvalues
            omega_n = np.sqrt(omega2_n.astype(complex))

            # Take care of imaginary frequencies
            if not np.all(omega2_n >= 0.):
                indices = np.where(omega2_n < 0)[0]
                print ("WARNING, %i imaginary frequencies at "
                       "q = (% 5.2f, % 5.2f, % 5.2f) ; (omega_q =% 5.3e*i)"
                       % (len(indices), q_c[0], q_c[1], q_c[2],
                          omega_n[indices][0].imag))
                
                omega_n[indices] = -1 * np.sqrt(np.abs(omega2_n[indices].real))

            omega_kn.append(omega_n.real)

        # Conversion factor from sqrt(Ha / Bohr**2 / amu) -> eV
        s = units.Hartree**0.5 * units._hbar * 1.e10 / \
            (units._e * units._amu)**(0.5) / units.Bohr
        # Convert to eV and Ang
        omega_kn = s * np.asarray(omega_kn)
        if modes:
            u_kn = np.asarray(u_kn) * units.Bohr
            return omega_kn, u_kn
        
        return omega_kn

    def write_modes(self, q_c, branches=0, kT=units.kB*300, repeat=(1, 1, 1),
                    nimages=30, acoustic=True):
        """Write mode to trajectory file.

        The classical equipartioning theorem states that each normal mode has
        an average energy::
        
            <E> = 1/2 * k_B * T = 1/2 * omega^2 * Q^2

                =>

              Q = sqrt(k_B*T) / omega

        at temperature T. Here, Q denotes the normal coordinate of the mode.
        
        Parameters
        ----------
        q_c: ndarray
            q-vector of the modes.
        branches: int or list
            Branch index of calculated modes.
        kT: float
            Temperature in units of eV. Determines the amplitude of the atomic
            displacements in the modes.
        repeat: tuple
            Repeat atoms (l, m, n) times in the directions of the lattice
            vectors. Displacements of atoms in repeated cells carry a Bloch
            phase factor given by the q-vector and the cell lattice vector R_m.
        nimages: int
            Number of images in an oscillation.
            
        """

        if isinstance(branches, int):
            branch_n = [branches]
        else:
            branch_n = list(branches)

        # Calculate modes
        omega_n, u_n = self.band_structure([q_c], modes=True, acoustic=acoustic)
        
        # Repeat atoms
        atoms = self.atoms * repeat
        pos_mav = atoms.positions.copy()
        # Total number of unit cells
        M = np.prod(repeat)
            
        # Corresponding lattice vectors R_m
        R_cm = np.indices(repeat[::-1]).reshape(3, -1)[::-1]
        # Bloch phase
        phase_m = np.exp(2.j * pi * np.dot(q_c, R_cm))
        phase_ma = phase_m.repeat(len(self.atoms))

     
        for n in branch_n:

            omega = omega_n[0, n]
            u_av = u_n[0, n] # .reshape((-1, 3))
            # Mean displacement at high T ?
            u_av *= sqrt(kT / abs(omega))
            
            mode_av = np.zeros((len(self.atoms), 3), dtype=self.dtype)
            indices = self.dyn.get_indices()
            mode_av[indices] = u_av
            mode_mav = (np.vstack([mode_av]*M) * phase_ma[:, np.newaxis]).real

            traj = PickleTrajectory('%s.mode.%d.traj' % (self.name, n), 'w')

            for x in np.linspace(0, 2*pi, nimages, endpoint=False):
                # XXX Is it correct to take out the sine component here ?
                atoms.set_positions(pos_mav + sin(x) * mode_mav)
                traj.write(atoms)
                
            traj.close()
Exemple #11
0
class HybridXC(XCFunctional):
    orbital_dependent = True

    def __init__(self, name, hybrid=None, xc=None, finegrid=False, alpha=None):
        """Mix standard functionals with exact exchange.

        name: str
            Name of hybrid functional.
        hybrid: float
            Fraction of exact exchange.
        xc: str or XCFunctional object
            Standard DFT functional with scaled down exchange.
        finegrid: boolean
            Use fine grid for energy functional evaluations?
        """

        if name == 'EXX':
            assert hybrid is None and xc is None
            hybrid = 1.0
            xc = XC(XCNull())
        elif name == 'PBE0':
            assert hybrid is None and xc is None
            hybrid = 0.25
            xc = XC('HYB_GGA_XC_PBEH')
        elif name == 'B3LYP':
            assert hybrid is None and xc is None
            hybrid = 0.2
            xc = XC('HYB_GGA_XC_B3LYP')

        if isinstance(xc, str):
            xc = XC(xc)

        self.hybrid = hybrid
        self.xc = xc
        self.type = xc.type
        self.alpha = alpha
        self.exx = 0.0

        XCFunctional.__init__(self, name)

    def get_setup_name(self):
        return 'PBE'

    def calculate_radial(self,
                         rgd,
                         n_sLg,
                         Y_L,
                         v_sg,
                         dndr_sLg=None,
                         rnablaY_Lv=None,
                         tau_sg=None,
                         dedtau_sg=None):
        return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg, dndr_sLg,
                                        rnablaY_Lv)

    def initialize(self, density, hamiltonian, wfs, occupations):
        self.xc.initialize(density, hamiltonian, wfs, occupations)
        self.nspins = wfs.nspins
        self.setups = wfs.setups
        self.density = density
        self.kpt_u = wfs.kpt_u

        self.gd = density.gd
        self.kd = wfs.kd
        self.bd = wfs.bd

        N_c = self.gd.N_c
        N = self.gd.N_c.prod()
        vol = self.gd.dv * N

        if self.alpha is None:
            self.alpha = 6 * vol**(2 / 3.0) / pi**2

        self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) *
                      self.kd.nbzkpts)
        ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max()

        if self.kd.N_c is None:
            self.bzk_kc = np.zeros((1, 3))
            dfghdfgh
        else:
            n = self.kd.N_c * 2 - 1
            bzk_kc = np.indices(n).transpose((1, 2, 3, 0))
            bzk_kc.shape = (-1, 3)
            bzk_kc -= self.kd.N_c - 1
            self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c

        self.pwd = PWDescriptor(ecut, self.gd, self.bzk_kc)

        n = 0
        for k_c, Gpk2_G in zip(self.bzk_kc[:], self.pwd.G2_qG):
            if (k_c > -0.5).all() and (k_c <= 0.5).all():  #XXX???
                if k_c.any():
                    self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G),
                                         Gpk2_G**-1)
                else:
                    self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]),
                                         Gpk2_G[1:]**-1)
                n += 1

        assert n == self.kd.N_c.prod()

        self.ghat = LFC(self.gd, [setup.ghat_l for setup in density.setups],
                        dtype=complex)
        self.ghat.set_k_points(self.bzk_kc)

        self.fullkd = KPointDescriptor(self.kd.bzk_kc, nspins=1)

        class S:
            id_a = []

            def set_symmetry(self, s):
                pass

        self.fullkd.set_symmetry(Atoms(pbc=True), S(), False)
        self.fullkd.set_communicator(world)
        self.pt = LFC(self.gd, [setup.pt_j for setup in density.setups],
                      dtype=complex)
        self.pt.set_k_points(self.fullkd.ibzk_kc)

        self.interpolator = density.interpolator

    def set_positions(self, spos_ac):
        self.ghat.set_positions(spos_ac)
        self.pt.set_positions(spos_ac)

    def calculate(self, gd, n_sg, v_sg=None, e_g=None):
        # Normal XC contribution:
        exc = self.xc.calculate(gd, n_sg, v_sg, e_g)

        # Add EXX contribution:
        return exc + self.exx

    def calculate_exx(self):
        """Non-selfconsistent calculation."""

        kd = self.kd
        K = self.fullkd.nibzkpts
        assert self.nspins == 1
        Q = K // world.size
        assert Q * world.size == K
        parallel = (world.size > self.nspins)

        self.exx = 0.0
        self.exx_skn = np.zeros((self.nspins, K, self.bd.nbands))

        kpt_u = []
        for k in range(world.rank * Q, (world.rank + 1) * Q):
            k_c = self.fullkd.ibzk_kc[k]
            for k1, k1_c in enumerate(kd.bzk_kc):
                if abs(k1_c - k_c).max() < 1e-10:
                    break

            # Index of symmetry related point in the irreducible BZ
            ik = kd.kibz_k[k1]
            kpt = self.kpt_u[ik]

            # KPoint from ground-state calculation
            phase_cd = np.exp(2j * pi * self.gd.sdisp_cd * k_c[:, np.newaxis])
            kpt2 = KPoint0(kpt.weight, kpt.s, k, None, phase_cd)
            kpt2.psit_nG = np.empty_like(kpt.psit_nG)
            kpt2.f_n = kpt.f_n / kpt.weight / K * 2
            for n, psit_G in enumerate(kpt2.psit_nG):
                psit_G[:] = kd.transform_wave_function(kpt.psit_nG[n], k1)

            kpt2.P_ani = self.pt.dict(len(kpt.psit_nG))
            self.pt.integrate(kpt2.psit_nG, kpt2.P_ani, k)
            kpt_u.append(kpt2)

        for s in range(self.nspins):
            kpt1_q = [KPoint(self.fullkd, kpt) for kpt in kpt_u if kpt.s == s]
            kpt2_q = kpt1_q[:]

            if len(kpt1_q) == 0:
                # No s-spins on this CPU:
                continue

            # Send rank:
            srank = self.fullkd.get_rank_and_index(s, (kpt1_q[0].k - 1) % K)[0]

            # Receive rank:
            rrank = self.fullkd.get_rank_and_index(s,
                                                   (kpt1_q[-1].k + 1) % K)[0]

            # Shift k-points K // 2 times:
            for i in range(K // 2 + 1):
                if i < K // 2:
                    if parallel:
                        kpt = kpt2_q[-1].next()
                        kpt.start_receiving(rrank)
                        kpt2_q[0].start_sending(srank)
                    else:
                        kpt = kpt2_q[0]

                for kpt1, kpt2 in zip(kpt1_q, kpt2_q):
                    if 2 * i == K:
                        self.apply(kpt1, kpt2, invert=(kpt1.k > kpt2.k))
                    else:
                        self.apply(kpt1, kpt2)
                        self.apply(kpt1, kpt2, invert=True)

                if i < K // 2:
                    if parallel:
                        kpt.wait()
                        kpt2_q[0].wait()
                    kpt2_q.pop(0)
                    kpt2_q.append(kpt)

        self.exx = world.sum(self.exx)
        world.sum(self.exx_skn)
        self.exx += self.calculate_paw_correction()

    def apply(self, kpt1, kpt2, invert=False):
        #print world.rank,kpt1.k,kpt2.k,invert
        k1_c = self.fullkd.ibzk_kc[kpt1.k]
        k2_c = self.fullkd.ibzk_kc[kpt2.k]
        if invert:
            k2_c = -k2_c
        k12_c = k1_c - k2_c
        N_c = self.gd.N_c
        eikr_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, k12_c / N_c).T)

        for q, k_c in enumerate(self.bzk_kc):
            if abs(k_c + k12_c).max() < 1e-9:
                q0 = q
                break

        for q, k_c in enumerate(self.bzk_kc):
            if abs(k_c - k12_c).max() < 1e-9:
                q00 = q
                break

        Gpk2_G = self.pwd.G2_qG[q0]
        if Gpk2_G[0] == 0:
            Gpk2_G = Gpk2_G.copy()
            Gpk2_G[0] = 1.0 / self.gamma

        N = N_c.prod()
        vol = self.gd.dv * N
        nspins = self.nspins

        same = (kpt1.k == kpt2.k)

        for n1, psit1_R in enumerate(kpt1.psit_nG):
            f1 = kpt1.f_n[n1]
            for n2, psit2_R in enumerate(kpt2.psit_nG):
                if same and n2 > n1:
                    continue

                f2 = kpt2.f_n[n2]

                nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, q0,
                                                   invert)

                nt_G = self.pwd.fft(nt_R * eikr_R) / N
                vt_G = nt_G.copy()
                vt_G *= -pi * vol / Gpk2_G
                e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid
                if same and n1 == n2:
                    e /= 2

                self.exx += e * f1 * f2
                self.ekin -= 2 * e * f1 * f2
                self.exx_skn[kpt1.s, kpt1.k, n1] += f2 * e
                self.exx_skn[kpt2.s, kpt2.k, n2] += f1 * e

                calculate_potential = not True
                if calculate_potential:
                    vt_R = self.pwd.ifft(vt_G).conj() * eikr_R * N / vol
                    if kpt1 is kpt2 and not invert and n1 == n2:
                        kpt1.vt_nG[n1] = 0.5 * f1 * vt_R

                    if invert:
                        kpt1.Htpsit_nG[n1] += \
                                           f2 * nspins * psit2_R.conj() * vt_R
                    else:
                        kpt1.Htpsit_nG[n1] += f2 * nspins * psit2_R * vt_R

                    if kpt1 is not kpt2:
                        if invert:
                            kpt2.Htpsit_nG[n2] += (f1 * nspins *
                                                   psit1_R.conj() * vt_R)
                        else:
                            kpt2.Htpsit_nG[n2] += (f1 * nspins * psit1_R *
                                                   vt_R.conj())

    def calculate_paw_correction(self):
        exx = 0
        deg = 2 // self.nspins  # spin degeneracy
        for a, D_sp in self.density.D_asp.items():
            setup = self.setups[a]
            for D_p in D_sp:
                D_ii = unpack2(D_p)
                ni = len(D_ii)

                for i1 in range(ni):
                    for i2 in range(ni):
                        A = 0.0
                        for i3 in range(ni):
                            p13 = packed_index(i1, i3, ni)
                            for i4 in range(ni):
                                p24 = packed_index(i2, i4, ni)
                                A += setup.M_pp[p13, p24] * D_ii[i3, i4]
                        p12 = packed_index(i1, i2, ni)
                        exx -= self.hybrid / deg * D_ii[i1, i2] * A

                if setup.X_p is not None:
                    exx -= self.hybrid * np.dot(D_p, setup.X_p)
            exx += self.hybrid * setup.ExxC
        return exx

    def calculate_pair_density(self, n1, n2, kpt1, kpt2, q, invert):
        if invert:
            nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2].conj()
        else:
            nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2]

        Q_aL = {}
        for a, P1_ni in kpt1.P_ani.items():
            P1_i = P1_ni[n1]
            P2_i = kpt2.P_ani[a][n2]
            if invert:
                D_ii = np.outer(P1_i.conj(), P2_i.conj())
            else:
                D_ii = np.outer(P1_i.conj(), P2_i)
            D_p = pack(D_ii)
            Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL)

        self.ghat.add(nt_G, Q_aL, q)
        return nt_G
Exemple #12
0
    def initialize(self, atoms=None):
        """Inexpensive initialization."""

        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        par = self.input_parameters

        world = par.communicator
        if world is None:
            world = mpi.world
        elif hasattr(world, 'new_communicator'):
            # Check for whether object has correct type already
            #
            # Using isinstance() is complicated because of all the
            # combinations, serial/parallel/debug...
            pass
        else:
            # world should be a list of ranks:
            world = mpi.world.new_communicator(np.asarray(world))
        self.wfs.world = world

        self.set_text(par.txt, par.verbose)

        natoms = len(atoms)

        pos_av = atoms.get_positions() / Bohr
        cell_cv = atoms.get_cell()
        pbc_c = atoms.get_pbc()
        Z_a = atoms.get_atomic_numbers()
        magmom_a = atoms.get_initial_magnetic_moments()

        magnetic = magmom_a.any()

        spinpol = par.spinpol
        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True

        if spinpol is None:
            spinpol = magnetic
        elif magnetic and not spinpol:
            raise ValueError('Non-zero initial magnetic moment for a '
                             'spin-paired calculation!')

        nspins = 1 + int(spinpol)
        
        if isinstance(par.xc, str):
            xc = XC(par.xc)
        else:
            xc = par.xc

        setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc, world)

        # K-point descriptor
        kd = KPointDescriptor(par.kpts, nspins)

        width = par.width
        if width is None:
            if kd.gamma:
                width = 0.0
            else:
                width = 0.1  # eV
        else:
            assert par.occupations is None
      
        if par.gpts is not None and par.h is None:
            N_c = np.array(par.gpts)
        else:
            if par.h is None:
                self.text('Using default value for grid spacing.')
                h = 0.2 
            else:
                h = par.h
            N_c = h2gpts(h, cell_cv)

        cell_cv /= Bohr


        if hasattr(self, 'time') or par.dtype==complex:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        kd.set_symmetry(atoms, setups, par.usesymm, N_c)

        nao = setups.nao
        nvalence = setups.nvalence - par.charge

        nbands = par.nbands
        if nbands is None:
            nbands = nao
        elif nbands > nao and par.mode == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: ' +
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        M = magmom_a.sum()
        if par.hund:
            f_si = setups[0].calculate_initial_occupation_numbers(
                magmom=0, hund=True, charge=par.charge, nspins=nspins)
            Mh = f_si[0].sum() - f_si[1].sum()
            if magnetic and M != Mh:
                raise RuntimeError('You specified a magmom that does not'
                                   'agree with hunds rule!')
            else:
                M = Mh

        if nbands <= 0:
            nbands = int(nvalence + M + 0.5) // 2 + (-nbands)

        if nvalence > 2 * nbands:
            raise ValueError('Too few bands!  Electrons: %d, bands: %d'
                             % (nvalence, nbands))

        if par.width is not None:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width).')
        if par.fixmom:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width, fixmagmom=True).')

        if self.occupations is None:
            if par.occupations is None:
                # Create object for occupation numbers:
                self.occupations = occupations.FermiDirac(width, par.fixmom)
            else:
                self.occupations = par.occupations

        self.occupations.magmom = M

        cc = par.convergence

        if par.mode == 'lcao':
            niter_fixdensity = 0
        else:
            niter_fixdensity = None

        if self.scf is None:
            self.scf = SCFLoop(
                cc['eigenstates'] * nvalence,
                cc['energy'] / Hartree * max(nvalence, 1),
                cc['density'] * nvalence,
                par.maxiter, par.fixdensity,
                niter_fixdensity)

        parsize, parsize_bands = par.parallel['domain'], par.parallel['band']

        if parsize_bands is None:
            parsize_bands = 1

        # TODO delete/restructure so all checks are in BandDescriptor
        if nbands % parsize_bands != 0:
            raise RuntimeError('Cannot distribute %d bands to %d processors' %
                               (nbands, parsize_bands))

        if not self.wfs:
            if parsize == 'domain only': #XXX this was silly!
                parsize = world.size

            domain_comm, kpt_comm, band_comm = mpi.distribute_cpus(parsize,
                parsize_bands, nspins, kd.nibzkpts, world, par.idiotproof)

            kd.set_communicator(kpt_comm)

            parstride_bands = par.parallel['stridebands']
            bd = BandDescriptor(nbands, band_comm, parstride_bands)

            if (self.density is not None and
                self.density.gd.comm.size != domain_comm.size):
                # Domain decomposition has changed, so we need to
                # reinitialize density and hamiltonian:
                if par.fixdensity:
                    raise RuntimeError("I'm confused - please specify parsize."
                                       )
                self.density = None
                self.hamiltonian = None

            # Construct grid descriptor for coarse grids for wave functions:
            gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c,
                                            domain_comm, parsize)

            # do k-point analysis here? XXX
            args = (gd, nvalence, setups, bd, dtype, world, kd, self.timer)

            if par.mode == 'lcao':
                # Layouts used for general diagonalizer
                sl_lcao = par.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = par.parallel['sl_default']
                lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao',
                                               gd, bd, dtype,
                                               nao=nao, timer=self.timer)

                self.wfs = LCAOWaveFunctions(lcaoksl, *args)
            elif par.mode == 'fd' or isinstance(par.mode, PW):
                # buffer_size keyword only relevant for fdpw
                buffer_size = par.parallel['buffer_size']
                # Layouts used for diagonalizer
                sl_diagonalize = par.parallel['sl_diagonalize']
                if sl_diagonalize is None:
                    sl_diagonalize = par.parallel['sl_default']
                diagksl = get_KohnSham_layouts(sl_diagonalize, 'fd',
                                               gd, bd, dtype,
                                               buffer_size=buffer_size,
                                               timer=self.timer)

                # Layouts used for orthonormalizer
                sl_inverse_cholesky = par.parallel['sl_inverse_cholesky']
                if sl_inverse_cholesky is None:
                    sl_inverse_cholesky = par.parallel['sl_default']
                if sl_inverse_cholesky != sl_diagonalize:
                    message = 'sl_inverse_cholesky != sl_diagonalize ' \
                        'is not implemented.'
                    raise NotImplementedError(message)
                orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd',
                                                gd, bd, dtype,
                                                buffer_size=buffer_size,
                                                timer=self.timer)

                # Use (at most) all available LCAO for initialization
                lcaonbands = min(nbands, nao)
                lcaobd = BandDescriptor(lcaonbands, band_comm, parstride_bands)
                assert nbands <= nao or bd.comm.size == 1
                assert lcaobd.mynbands == min(bd.mynbands, nao) #XXX

                # Layouts used for general diagonalizer (LCAO initialization)
                sl_lcao = par.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = par.parallel['sl_default']
                initksl = get_KohnSham_layouts(sl_lcao, 'lcao',
                                               gd, lcaobd, dtype, 
                                               nao=nao,
                                               timer=self.timer)

                if par.mode == 'fd':
                    self.wfs = FDWaveFunctions(par.stencils[0], diagksl,
                                               orthoksl, initksl, *args)
                else:
                    # Planewave basis:
                    self.wfs = par.mode(diagksl, orthoksl, initksl,
                                        gd, nvalence, setups, bd,
                                        world, kd, self.timer)
            else:
                self.wfs = par.mode(self, *args)
        else:
            self.wfs.set_setups(setups)

        if not self.wfs.eigensolver:
            # Number of bands to converge:
            nbands_converge = cc['bands']
            if nbands_converge == 'all':
                nbands_converge = nbands
            elif nbands_converge != 'occupied':
                assert isinstance(nbands_converge, int)
                if nbands_converge < 0:
                    nbands_converge += nbands
            eigensolver = get_eigensolver(par.eigensolver, par.mode,
                                          par.convergence)
            eigensolver.nbands_converge = nbands_converge
            # XXX Eigensolver class doesn't define an nbands_converge property
            self.wfs.set_eigensolver(eigensolver)

        if self.density is None:
            gd = self.wfs.gd
            if par.stencils[1] != 9:
                # Construct grid descriptor for fine grids for densities
                # and potentials:
                finegd = gd.refine()
            else:
                # Special case (use only coarse grid):
                finegd = gd
            self.density = Density(gd, finegd, nspins,
                                   par.charge + setups.core_charge)

        self.density.initialize(setups, par.stencils[1], self.timer,
                                magmom_a, par.hund)
        self.density.set_mixer(par.mixer)

        if self.hamiltonian is None:
            gd, finegd = self.density.gd, self.density.finegd
            self.hamiltonian = Hamiltonian(gd, finegd, nspins,
                                           setups, par.stencils[1], self.timer,
                                           xc, par.poissonsolver,
                                           par.external)

        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)

        self.text()
        self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth)
        self.txt.flush()

        if dry_run:
            self.dry_run()

        self.initialized = True
Exemple #13
0
class G0W0(PairDensity):
    """This class defines the G0W0 calculator. The G0W0 calculator is used
    is used to calculate the quasi particle energies through the G0W0
    approximation for a number of states.

    Note: So far the G0W0 calculation only works for spin-paired systems.

    Parameters:
       calc: str or PAW object
          GPAW calculator object or filename of saved calculator object.
       filename: str
          Base filename of output files.
       kpts: list
          List of indices of the IBZ k-points to calculate the quasi particle
          energies for.
       bands: tuple
          Range of band indices, like (n1, n2+1), to calculate the quasi
          particle energies for. Note that the second band index is not
          included.
       ecut: float
          Plane wave cut-off energy in eV.
       nbands: int
          Number of bands to use in the calculation. If :None: the number will
          be determined from :ecut: to yield a number close to the number of
          plane waves used.
       ppa: bool
          Sets whether the Godby-Needs plasmon-pole approximation for the
          dielectric function should be used.
       E0: float
          Energy (in eV) used for fitting in the plasmon-pole approximation.
       domega0: float
          Minimum frequency step (in eV) used in the generation of the non-
          linear frequency grid.
       omega2: float
          Control parameter for the non-linear frequency grid, equal to the
          frequency where the grid spacing has doubled in size.
       wstc: bool
          Sets whether a Wigner-Seitz truncation should be used for the
          Coloumb potential.
    """
    def __init__(self, calc, filename='gw',
                 kpts=None, bands=None, nbands=None, ppa=False,
                 wstc=False,
                 ecut=150.0, eta=0.1, E0=1.0 * Hartree,
                 domega0=0.025, omega2=10.0,
                 nblocks=1, savew=False,
                 world=mpi.world):

        if world.rank != 0:
            txt = devnull
        else:
            txt = open(filename + '.txt', 'w')

        p = functools.partial(print, file=txt)
        p('  ___  _ _ _ ')
        p(' |   || | | |')
        p(' | | || | | |')
        p(' |__ ||_____|')
        p(' |___|')
        p()

        PairDensity.__init__(self, calc, ecut, world=world, nblocks=nblocks,
                             txt=txt)

        self.filename = filename
        self.savew = savew
        
        ecut /= Hartree
        
        self.ppa = ppa
        self.wstc = wstc
        self.eta = eta / Hartree
        self.E0 = E0 / Hartree
        self.domega0 = domega0 / Hartree
        self.omega2 = omega2 / Hartree

        self.kpts = select_kpts(kpts, self.calc)
                
        if bands is None:
            bands = [0, self.nocc2]
            
        self.bands = bands

        b1, b2 = bands
        self.shape = shape = (self.calc.wfs.nspins, len(self.kpts), b2 - b1)
        self.eps_sin = np.empty(shape)     # KS-eigenvalues
        self.f_sin = np.empty(shape)       # occupation numbers
        self.sigma_sin = np.zeros(shape)   # self-energies
        self.dsigma_sin = np.zeros(shape)  # derivatives of self-energies
        self.vxc_sin = None                # KS XC-contributions
        self.exx_sin = None                # exact exchange contributions
        self.Z_sin = None                  # renormalization factors
        
        if nbands is None:
            nbands = int(self.vol * ecut**1.5 * 2**0.5 / 3 / pi**2)
        self.nbands = nbands

        p()
        p('Quasi particle states:')
        if kpts is None:
            p('All k-points in IBZ')
        else:
            kptstxt = ', '.join(['{0:d}'.format(k) for k in self.kpts])
            p('k-points (IBZ indices): [' + kptstxt + ']')
        p('Band range: ({0:d}, {1:d})'.format(b1, b2))
        p()
        p('Computational parameters:')
        p('Plane wave cut-off: {0:g} eV'.format(self.ecut * Hartree))
        p('Number of bands: {0:d}'.format(self.nbands))
        p('Broadening: {0:g} eV'.format(self.eta * Hartree))

        kd = self.calc.wfs.kd

        self.mysKn1n2 = None  # my (s, K, n1, n2) indices
        self.distribute_k_points_and_bands(b1, b2, kd.ibz2bz_k[self.kpts])
        
        # Find q-vectors and weights in the IBZ:
        assert -1 not in kd.bz2bz_ks
        offset_c = 0.5 * ((kd.N_c + 1) % 2) / kd.N_c
        bzq_qc = monkhorst_pack(kd.N_c) + offset_c
        self.qd = KPointDescriptor(bzq_qc)
        self.qd.set_symmetry(self.calc.atoms, kd.symmetry)
        
        assert self.calc.wfs.nspins == 1
        
    @timer('G0W0')
    def calculate(self, ecuts=None):
        """Starts the G0W0 calculation. Returns a dict with the results with
        the following key/value pairs:

        f: (s, k, n) ndarray
           Occupation numbers
        eps: (s, k, n) ndarray
           Kohn-Sham eigenvalues in eV
        vxc: (s, k, n) ndarray
           Exchange-correlation contributions in eV
        exx: (s, k, n) ndarray
           Exact exchange contributions in eV
        sigma: (s, k, n) ndarray
           Self-energy contributions in eV
        Z: (s, k, n) ndarray
           Renormalization factors
        qp: (s, k, n) ndarray
           Quasi particle energies in eV
        """
        kd = self.calc.wfs.kd

        self.calculate_ks_xc_contribution()
        self.calculate_exact_exchange()

        # Reset calculation
        self.sigma_sin = np.zeros(self.shape)   # self-energies
        self.dsigma_sin = np.zeros(self.shape)  # derivatives of self-energies

        # Get KS eigenvalues and occupation numbers:
        b1, b2 = self.bands
        for i, k in enumerate(self.kpts):
            kpt = self.calc.wfs.kpt_u[k]
            self.eps_sin[0, i] = kpt.eps_n[b1:b2]
            self.f_sin[0, i] = kpt.f_n[b1:b2] / kpt.weight

        # My part of the states we want to calculate QP-energies for:
        mykpts = [self.get_k_point(s, K, n1, n2)
                  for s, K, n1, n2 in self.mysKn1n2]
        
        # Loop over q in the IBZ:
        for pd0, W0, q_c in self.calculate_screened_potential():
            for kpt1 in mykpts:
                K2 = kd.find_k_plus_q(q_c, [kpt1.K])[0]
                kpt2 = self.get_k_point(0, K2, 0, self.nbands, block=True)
                k1 = kd.bz2ibz_k[kpt1.K]
                i = self.kpts.index(k1)
                self.calculate_q(i, kpt1, kpt2, pd0, W0)

        self.world.sum(self.sigma_sin)
        self.world.sum(self.dsigma_sin)
        
        self.Z_sin = 1 / (1 - self.dsigma_sin)
        self.qp_sin = self.eps_sin + self.Z_sin * (self.sigma_sin +
                                                   self.exx_sin -
                                                   self.vxc_sin)
        
        results = {'f': self.f_sin,
                   'eps': self.eps_sin * Hartree,
                   'vxc': self.vxc_sin * Hartree,
                   'exx': self.exx_sin * Hartree,
                   'sigma': self.sigma_sin * Hartree,
                   'Z': self.Z_sin,
                   'qp': self.qp_sin * Hartree}
      
        self.print_results(results)
        
        return results
        
    def calculate_q(self, i, kpt1, kpt2, pd0, W0):
        """Calculates the contribution to the self-energy and its derivative
        for a given set of k-points, kpt1 and kpt2."""
        wfs = self.calc.wfs
        
        N_c = pd0.gd.N_c
        i_cG = self.sign * np.dot(self.U_cc,
                                  np.unravel_index(pd0.Q_qG[0], N_c))

        q_c = wfs.kd.bzk_kc[kpt2.K] - wfs.kd.bzk_kc[kpt1.K]
        q0 = np.allclose(q_c, 0) and not self.wstc

        shift0_c = q_c - self.sign * np.dot(self.U_cc, pd0.kd.bzk_kc[0])
        assert np.allclose(shift0_c.round(), shift0_c)
        shift0_c = shift0_c.round().astype(int)

        shift_c = kpt1.shift_c - kpt2.shift_c - shift0_c
        I_G = np.ravel_multi_index(i_cG + shift_c[:, None], N_c, 'wrap')
        
        G_Gv = pd0.get_reciprocal_vectors()
        pos_av = np.dot(self.spos_ac, pd0.gd.cell_cv)
        M_vv = np.dot(pd0.gd.cell_cv.T,
                      np.dot(self.U_cc.T,
                             np.linalg.inv(pd0.gd.cell_cv).T))
        
        Q_aGii = []
        for a, Q_Gii in enumerate(self.Q_aGii):
            x_G = np.exp(1j * np.dot(G_Gv, (pos_av[a] -
                                            self.sign *
                                            np.dot(M_vv, pos_av[a]))))
            U_ii = self.calc.wfs.setups[a].R_sii[self.s]
            Q_Gii = np.dot(np.dot(U_ii, Q_Gii * x_G[:, None, None]),
                           U_ii.T).transpose(1, 0, 2)
            Q_aGii.append(Q_Gii)

        if debug:
            self.check(i_cG, shift0_c, N_c, q_c, Q_aGii)
                
        if self.ppa:
            calculate_sigma = self.calculate_sigma_ppa
        else:
            calculate_sigma = self.calculate_sigma
            
        for n in range(kpt1.n2 - kpt1.n1):
            ut1cc_R = kpt1.ut_nR[n].conj()
            eps1 = kpt1.eps_n[n]
            C1_aGi = [np.dot(Qa_Gii, P1_ni[n].conj())
                      for Qa_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani)]
            n_mG = self.calculate_pair_densities(ut1cc_R, C1_aGi, kpt2,
                                                 pd0, I_G)
            if self.sign == 1:
                n_mG = n_mG.conj()
                
            if q0:
                n_mG[:, 0] = 0
                m = n + kpt1.n1 - kpt2.n1
                if 0 <= m < len(n_mG):
                    n_mG[m, 0] = 1.0
                    
            f_m = kpt2.f_n
            deps_m = eps1 - kpt2.eps_n
            sigma, dsigma = calculate_sigma(n_mG, deps_m, f_m, W0)
            nn = kpt1.n1 + n - self.bands[0]
            self.sigma_sin[kpt1.s, i, nn] += sigma
            self.dsigma_sin[kpt1.s, i, nn] += dsigma
            
    def check(self, i_cG, shift0_c, N_c, q_c, Q_aGii):
        I0_G = np.ravel_multi_index(i_cG - shift0_c[:, None], N_c, 'wrap')
        qd1 = KPointDescriptor([q_c])
        pd1 = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd1)
        G_I = np.empty(N_c.prod(), int)
        G_I[:] = -1
        I1_G = pd1.Q_qG[0]
        G_I[I1_G] = np.arange(len(I0_G))
        G_G = G_I[I0_G]
        assert len(I0_G) == len(I1_G)
        assert (G_G >= 0).all()

        for a, Q_Gii in enumerate(self.initialize_paw_corrections(pd1)):
            e = abs(Q_aGii[a] - Q_Gii[G_G]).max()
            assert e < 1e-12

    @timer('Sigma')
    def calculate_sigma(self, n_mG, deps_m, f_m, C_swGG):
        """Calculates a contribution to the self-energy and its derivative for
        a given (k, k-q)-pair from its corresponding pair-density and
        energy."""
        o_m = abs(deps_m)
        # Add small number to avoid zeros for degenerate states:
        sgn_m = np.sign(deps_m + 1e-15)
        
        # Pick +i*eta or -i*eta:
        s_m = (1 + sgn_m * np.sign(0.5 - f_m)).astype(int) // 2
        
        beta = (2**0.5 - 1) * self.domega0 / self.omega2
        w_m = (o_m / (self.domega0 + beta * o_m)).astype(int)
        o1_m = self.omega_w[w_m]
        o2_m = self.omega_w[w_m + 1]
        
        x = 1.0 / (self.qd.nbzkpts * 2 * pi * self.vol)
        sigma = 0.0
        dsigma = 0.0
        # Performing frequency integration
        for o, o1, o2, sgn, s, w, n_G in zip(o_m, o1_m, o2_m,
                                             sgn_m, s_m, w_m, n_mG):
            C1_GG = C_swGG[s][w]
            C2_GG = C_swGG[s][w + 1]
            p = x * sgn
            myn_G = n_G[self.Ga:self.Gb]
            sigma1 = p * np.dot(np.dot(myn_G, C1_GG), n_G.conj()).imag
            sigma2 = p * np.dot(np.dot(myn_G, C2_GG), n_G.conj()).imag
            sigma += ((o - o1) * sigma2 + (o2 - o) * sigma1) / (o2 - o1)
            dsigma += sgn * (sigma2 - sigma1) / (o2 - o1)
            
        return sigma, dsigma

    def calculate_screened_potential(self):
        """Calculates the screened potential for each q-point in the 1st BZ.
        Since many q-points are related by symmetry, the actual calculation is
        only done for q-points in the IBZ and the rest are obtained by symmetry
        transformations. Results are returned as a generator to that it is not
        necessary to store a huge matrix for each q-point in the memory."""
        # The decorator $timer('W') doesn't work for generators, do we will
        # have to manually start and stop the timer here:
        self.timer.start('W')
        print('Calculating screened Coulomb potential', file=self.fd)
        if self.wstc:
            print('Using Wigner-Seitz truncated Coloumb potential',
                  file=self.fd)
            
        if self.ppa:
            print('Using Godby-Needs plasmon-pole approximation:',
                  file=self.fd)
            print('    Fitting energy: i*E0, E0 = %.3f Hartee' % self.E0,
                  file=self.fd)

            # use small imaginary frequency to avoid dividing by zero:
            frequencies = [1e-10j, 1j * self.E0 * Hartree]
            
            parameters = {'eta': 0,
                          'hilbert': False,
                          'timeordered': False,
                          'frequencies': frequencies}
        else:
            print('Using full frequency integration:', file=self.fd)
            print('  domega0: {0:g}'.format(self.domega0 * Hartree),
                  file=self.fd)
            print('  omega2: {0:g}'.format(self.omega2 * Hartree),
                  file=self.fd)

            parameters = {'eta': self.eta * Hartree,
                          'hilbert': True,
                          'timeordered': True,
                          'domega0': self.domega0 * Hartree,
                          'omega2': self.omega2 * Hartree}
        
        chi0 = Chi0(self.calc,
                    nbands=self.nbands,
                    ecut=self.ecut * Hartree,
                    intraband=False,
                    real_space_derivatives=False,
                    txt=self.filename + '.w.txt',
                    timer=self.timer,
                    keep_occupied_states=True,
                    nblocks=self.blockcomm.size,
                    no_optical_limit=self.wstc,
                    **parameters)

        if self.wstc:
            wstc = WignerSeitzTruncatedCoulomb(
                self.calc.wfs.gd.cell_cv,
                self.calc.wfs.kd.N_c,
                chi0.fd)
        else:
            wstc = None
        
        self.omega_w = chi0.omega_w
        self.omegamax = chi0.omegamax
        
        htp = HilbertTransform(self.omega_w, self.eta, gw=True)
        htm = HilbertTransform(self.omega_w, -self.eta, gw=True)

        # Find maximum size of chi-0 matrices:
        gd = self.calc.wfs.gd
        nGmax = max(count_reciprocal_vectors(self.ecut, gd, q_c)
                    for q_c in self.qd.ibzk_kc)
        nw = len(self.omega_w)
        
        size = self.blockcomm.size
        mynGmax = (nGmax + size - 1) // size
        mynw = (nw + size - 1) // size
        
        # Allocate memory in the beginning and use for all q:
        A1_x = np.empty(nw * mynGmax * nGmax, complex)
        A2_x = np.empty(max(mynw * nGmax, nw * mynGmax) * nGmax, complex)
        
        # Need to pause the timer in between iterations
        self.timer.stop('W')
        for iq, q_c in enumerate(self.qd.ibzk_kc):
            self.timer.start('W')
            if self.savew:
                wfilename = self.filename + '.w.q%d.pckl' % iq
                fd = opencew(wfilename)
            if self.savew and fd is None:
                # Read screened potential from file
                with open(wfilename) as fd:
                    pd, W = pickle.load(fd)
            else:
                # First time calculation
                pd, W = self.calculate_w(chi0, q_c, htp, htm, wstc, A1_x, A2_x)
                if self.savew:
                    pickle.dump((pd, W), fd, pickle.HIGHEST_PROTOCOL)

            self.timer.stop('W')
            # Loop over all k-points in the BZ and find those that are related
            # to the current IBZ k-point by symmetry
            Q1 = self.qd.ibz2bz_k[iq]
            done = set()
            for s, Q2 in enumerate(self.qd.bz2bz_ks[Q1]):
                if Q2 >= 0 and Q2 not in done:
                    s = self.qd.sym_k[Q2]
                    self.s = s
                    self.U_cc = self.qd.symmetry.op_scc[s]
                    time_reversal = self.qd.time_reversal_k[Q2]
                    self.sign = 1 - 2 * time_reversal
                    Q_c = self.qd.bzk_kc[Q2]
                    d_c = self.sign * np.dot(self.U_cc, q_c) - Q_c
                    assert np.allclose(d_c.round(), d_c)
                    yield pd, W, Q_c
                    done.add(Q2)
    
    @timer('WW')
    def calculate_w(self, chi0, q_c, htp, htm, wstc, A1_x, A2_x):
        """Calculates the screened potential for a specified q-point."""
        pd, chi0_wGG = chi0.calculate(q_c, A_x=A1_x)[:2]
        self.Q_aGii = chi0.Q_aGii
        self.Ga = chi0.Ga
        self.Gb = chi0.Gb
        
        if self.blockcomm.size > 1:
            A1_x = chi0_wGG.ravel()
            chi0_wGG = chi0.redistribute(chi0_wGG, A2_x)
            
        if self.wstc:
            iG_G = (wstc.get_potential(pd) / (4 * pi))**0.5
            if np.allclose(q_c, 0):
                chi0_wGG[:, 0] = 0.0
                chi0_wGG[:, :, 0] = 0.0
                G0inv = 0.0
                G20inv = 0.0
            else:
                G0inv = None
                G20inv = None
        else:
            if np.allclose(q_c, 0):
                dq3 = (2 * pi)**3 / (self.qd.nbzkpts * self.vol)
                qc = (dq3 / 4 / pi * 3)**(1 / 3)
                G0inv = 2 * pi * qc**2 / dq3
                G20inv = 4 * pi * qc / dq3
                G_G = pd.G2_qG[0]**0.5
                G_G[0] = 1
                iG_G = 1 / G_G
            else:
                iG_G = pd.G2_qG[0]**-0.5
                G0inv = None
                G20inv = None

        delta_GG = np.eye(len(iG_G))

        if self.ppa:
            return pd, self.ppa_w(chi0_wGG, iG_G, delta_GG, G0inv, G20inv, q_c)
            
        self.timer.start('Dyson eq.')
        # Calculate W and store it in chi0_wGG ndarray:
        for chi0_GG in chi0_wGG:
            e_GG = (delta_GG -
                    4 * pi * chi0_GG * iG_G * iG_G[:, np.newaxis])
            W_GG = chi0_GG
            W_GG[:] = 4 * pi * (np.linalg.inv(e_GG) -
                                delta_GG) * iG_G * iG_G[:, np.newaxis]
            if np.allclose(q_c, 0):
                W_GG[0, 0] *= G20inv
                W_GG[1:, 0] *= G0inv
                W_GG[0, 1:] *= G0inv
                
        if self.blockcomm.size > 1:
            Wm_wGG = chi0.redistribute(chi0_wGG, A1_x)
        else:
            Wm_wGG = chi0_wGG
            
        Wp_wGG = A2_x[:Wm_wGG.size].reshape(Wm_wGG.shape)
        Wp_wGG[:] = Wm_wGG

        with self.timer('Hilbert transform'):
            htp(Wp_wGG)
            htm(Wm_wGG)
        self.timer.stop('Dyson eq.')
        
        return pd, [Wp_wGG, Wm_wGG]

    @timer('Kohn-Sham XC-contribution')
    def calculate_ks_xc_contribution(self):
        name = self.filename + '.vxc.npy'
        fd = opencew(name)
        if fd is None:
            print('Reading Kohn-Sham XC contribution from file:', name,
                  file=self.fd)
            with open(name) as fd:
                self.vxc_sin = np.load(fd)
            assert self.vxc_sin.shape == self.shape, self.vxc_sin.shape
            return
            
        print('Calculating Kohn-Sham XC contribution', file=self.fd)
        if self.reader is not None:
            self.calc.wfs.read_projections(self.reader)
        vxc_skn = vxc(self.calc, self.calc.hamiltonian.xc) / Hartree
        n1, n2 = self.bands
        self.vxc_sin = vxc_skn[:, self.kpts, n1:n2]
        np.save(fd, self.vxc_sin)
        
    @timer('EXX')
    def calculate_exact_exchange(self):
        name = self.filename + '.exx.npy'
        fd = opencew(name)
        if fd is None:
            print('Reading EXX contribution from file:', name, file=self.fd)
            with open(name) as fd:
                self.exx_sin = np.load(fd)
            assert self.exx_sin.shape == self.shape, self.exx_sin.shape
            return
            
        print('Calculating EXX contribution', file=self.fd)
        exx = EXX(self.calc, kpts=self.kpts, bands=self.bands,
                  txt=self.filename + '.exx.txt', timer=self.timer)
        exx.calculate()
        self.exx_sin = exx.get_eigenvalue_contributions() / Hartree
        np.save(fd, self.exx_sin)

    def print_results(self, results):
        description = ['f:     Occupation numbers',
                       'eps:   KS-eigenvalues [eV]',
                       'vxc:   KS vxc [eV]',
                       'exx:   Exact exchange [eV]',
                       'sigma: Self-energies [eV]',
                       'Z:     Renormalization factors',
                       'qp:    QP-energies [eV]']

        print('\nResults:', file=self.fd)
        for line in description:
            print(line, file=self.fd)
            
        b1, b2 = self.bands
        names = [line.split(':', 1)[0] for line in description]
        ibzk_kc = self.calc.wfs.kd.ibzk_kc
        for s in range(self.calc.wfs.nspins):
            for i, ik in enumerate(self.kpts):
                print('\nk-point ' +
                      '{0} ({1}): ({2:.3f}, {3:.3f}, {4:.3f})'.format(
                          i, ik, *ibzk_kc[ik]), file=self.fd)
                print('band' +
                      ''.join('{0:>8}'.format(name) for name in names),
                      file=self.fd)
                for n in range(b2 - b1):
                    print('{0:4}'.format(n + b1) +
                          ''.join('{0:8.3f}'.format(results[name][s, i, n])
                                  for name in names),
                          file=self.fd)

        self.timer.write(self.fd)

    @timer('PPA')
    def ppa_w(self, chi0_wGG, iG_G, delta_GG, G0inv, G20inv, q_c):
        einv_wGG = []
        for chi0_GG in chi0_wGG:
            e_GG = (delta_GG -
                    4 * pi * chi0_GG * iG_G * iG_G[:, np.newaxis])
            einv_wGG.append(np.linalg.inv(e_GG) - delta_GG)

        if self.wstc and np.allclose(q_c, 0):
            einv_wGG[0][0] = 42
            einv_wGG[0][:, 0] = 42
        omegat_GG = self.E0 * np.sqrt(einv_wGG[1] /
                                      (einv_wGG[0] - einv_wGG[1]))
        R_GG = -0.5 * omegat_GG * einv_wGG[0]
        W_GG = 4 * pi**2 * R_GG * iG_G * iG_G[:, np.newaxis]
        if np.allclose(q_c, 0):
            W_GG[0, 0] *= G20inv
            W_GG[1:, 0] *= G0inv
            W_GG[0, 1:] *= G0inv

        return [W_GG, omegat_GG]
        
    @timer('PPA-Sigma')
    def calculate_sigma_ppa(self, n_mG, deps_m, f_m, W):
        W_GG, omegat_GG = W

        sigma = 0.0
        dsigma = 0.0

        # init variables (is this necessary?)
        nG = n_mG.shape[1]
        deps_GG = np.empty((nG, nG))
        sign_GG = np.empty((nG, nG))
        x1_GG = np.empty((nG, nG))
        x2_GG = np.empty((nG, nG))
        x3_GG = np.empty((nG, nG))
        x4_GG = np.empty((nG, nG))
        x_GG = np.empty((nG, nG))
        dx_GG = np.empty((nG, nG))
        nW_G = np.empty(nG)
        for m in range(np.shape(n_mG)[0]):
            deps_GG = deps_m[m]
            sign_GG = 2 * f_m[m] - 1
            x1_GG = 1 / (deps_GG + omegat_GG - 1j * self.eta)
            x2_GG = 1 / (deps_GG - omegat_GG + 1j * self.eta)
            x3_GG = 1 / (deps_GG + omegat_GG - 1j * self.eta * sign_GG)
            x4_GG = 1 / (deps_GG - omegat_GG - 1j * self.eta * sign_GG)
            x_GG = W_GG * (sign_GG * (x1_GG - x2_GG) + x3_GG + x4_GG)
            dx_GG = W_GG * (sign_GG * (x1_GG**2 - x2_GG**2) +
                            x3_GG**2 + x4_GG**2)
            nW_G = np.dot(n_mG[m], x_GG)
            sigma += np.vdot(n_mG[m], nW_G).real
            nW_G = np.dot(n_mG[m], dx_GG)
            dsigma -= np.vdot(n_mG[m], nW_G).real
        
        x = 1 / (self.qd.nbzkpts * 2 * pi * self.vol)
        return x * sigma, x * dsigma
Exemple #14
0
class BSE:
    def __init__(self,
                 calc=None,
                 spinors=False,
                 ecut=10.,
                 scale=1.0,
                 nbands=None,
                 valence_bands=None,
                 conduction_bands=None,
                 eshift=None,
                 gw_skn=None,
                 truncation=None,
                 integrate_gamma=1,
                 txt=sys.stdout,
                 mode='BSE',
                 wfile=None,
                 write_h=False,
                 write_v=False):

        """Creates the BSE object

        calc: str or calculator object
            The string should refer to the .gpw file contaning KS orbitals
        ecut: float
            Plane wave cutoff energy (eV)
        nbands: int
            Number of bands used for the screened interaction
        valence_bands: list
            Valence bands used in the BSE Hamiltonian
        conduction_bands: list
            Conduction bands used in the BSE Hamiltonian
        eshift: float
            Scissors operator opening the gap (eV)
        gw_skn: list / array
            List or array defining the gw quasiparticle energies used in
            the BSE Hamiltonian. Should match spin, k-points and
            valence/conduction bands
        truncation: str
            Coulomb truncation scheme. Can be either wigner-seitz,
            2D, 1D, or 0D
        integrate_gamma: int
            Method to integrate the Coulomb interaction. 1 is a numerical
            integration at all q-points with G=[0,0,0] - this breaks the
            symmetry slightly. 0 is analytical integration at q=[0,0,0] only -
            this conserves the symmetry. integrate_gamma=2 is the same as 1,
            but the average is only carried out in the non-periodic directions.
        txt: str
            txt output
        mode: str
            Theory level used. can be RPA TDHF or BSE. Only BSE is screened.
        wfile: str
            File for saving screened interaction and some other stuff
            needed later
        write_h: bool
            If True, write the BSE Hamiltonian to H_SS.ulm.
        write_v: bool
            If True, write eigenvalues and eigenstates to v_TS.ulm
        """

        # Calculator
        if isinstance(calc, str):
            calc = GPAW(calc, txt=None, communicator=serial_comm)
        self.calc = calc
        self.spinors = spinors
        self.scale = scale

        assert mode in ['RPA', 'TDHF', 'BSE']
        # assert calc.wfs.kd.nbzkpts % world.size == 0

        # txt file
        if world.rank != 0:
            txt = devnull
        elif isinstance(txt, str):
            txt = open(txt, 'w', 1)
        self.fd = txt

        self.ecut = ecut / Hartree
        self.nbands = nbands
        self.mode = mode
        self.truncation = truncation
        if integrate_gamma == 0 and truncation is not None:
            print('***WARNING*** Analytical Coulomb integration is ' +
                  'not expected to work with Coulomb truncation. ' +
                  'Use integrate_gamma=1', file=self.fd)
        self.integrate_gamma = integrate_gamma
        self.wfile = wfile
        self.write_h = write_h
        self.write_v = write_v

        # Find q-vectors and weights in the IBZ:
        self.kd = calc.wfs.kd
        if -1 in self.kd.bz2bz_ks:
            print('***WARNING*** Symmetries may not be right ' +
                  'Use gamma-centered grid to be sure', file=self.fd)
        offset_c = 0.5 * ((self.kd.N_c + 1) % 2) / self.kd.N_c
        bzq_qc = monkhorst_pack(self.kd.N_c) + offset_c
        self.qd = KPointDescriptor(bzq_qc)
        self.qd.set_symmetry(self.calc.atoms, self.kd.symmetry)
        self.vol = abs(np.linalg.det(calc.wfs.gd.cell_cv))

        # bands
        self.spins = self.calc.wfs.nspins
        if self.spins == 2:
            if self.spinors:
                self.spinors = False
                print('***WARNING*** Presently the spinor version' +
                      'does not work for spin-polarized calculations.' +
                      'Performing scalar calculation', file=self.fd)
            assert len(valence_bands[0]) == len(valence_bands[1])
            assert len(conduction_bands[0]) == len(conduction_bands[1])
        if valence_bands is None:
            nv = self.calc.wfs.setups.nvalence
            valence_bands = [[nv // 2 - 1]]
            if self.spins == 2:
                valence_bands *= 2
        if conduction_bands is None:
            conduction_bands = [[valence_bands[-1] + 1]]
            if self.spins == 2:
                conduction_bands *= 2

        self.val_sn = np.array(valence_bands)
        if len(np.shape(self.val_sn)) == 1:
            self.val_sn = np.array([self.val_sn])
        self.con_sn = np.array(conduction_bands)
        if len(np.shape(self.con_sn)) == 1:
            self.con_sn = np.array([self.con_sn])

        self.td = True
        for n in self.val_sn[0]:
            if n in self.con_sn[0]:
                self.td = False
        if len(self.val_sn) == 2:
            for n in self.val_sn[1]:
                if n in self.con_sn[1]:
                    self.td = False

        self.nv = len(self.val_sn[0])
        self.nc = len(self.con_sn[0])
        if eshift is not None:
            eshift /= Hartree
        if gw_skn is not None:
            assert self.nv + self.nc == len(gw_skn[0, 0])
            assert self.kd.nibzkpts == len(gw_skn[0])
            gw_skn = gw_skn[:, self.kd.bz2ibz_k]
            # assert self.kd.nbzkpts == len(gw_skn[0])
            gw_skn /= Hartree
        self.gw_skn = gw_skn
        self.eshift = eshift

        # Number of pair orbitals
        self.nS = self.kd.nbzkpts * self.nv * self.nc * self.spins
        self.nS *= (self.spinors + 1)**2

        # Wigner-Seitz stuff
        if self.truncation == 'wigner-seitz':
            self.wstc = WignerSeitzTruncatedCoulomb(self.calc.wfs.gd.cell_cv,
                                                    self.kd.N_c, self.fd)
        else:
            self.wstc = None

        self.print_initialization(self.td, self.eshift, self.gw_skn)

    def calculate(self, optical=True, ac=1.0):

        if self.spinors:
            """Calculate spinors. Here m is index of eigenvalues with SOC
            and n is the basis of eigenstates withour SOC. Below m is used
            for unoccupied states and n is used for occupied states so be
            careful!"""

            print('Diagonalizing spin-orbit Hamiltonian', file=self.fd)
            param = self.calc.parameters
            if not param['symmetry'] == 'off':
                print('Calculating KS wavefunctions without symmetry ' +
                      'for spin-orbit', file=self.fd)
                if not op.isfile('gs_nosym.gpw'):
                    calc_so = GPAW(**param)
                    calc_so.set(symmetry='off',
                                fixdensity=True,
                                txt='gs_nosym.txt')
                    calc_so.atoms = self.calc.atoms
                    calc_so.density = self.calc.density
                    calc_so.get_potential_energy()
                    calc_so.write('gs_nosym.gpw')
                calc_so = GPAW('gs_nosym.gpw', txt=None,
                               communicator=serial_comm)
                e_mk, v_knm = get_spinorbit_eigenvalues(calc_so,
                                                        return_wfs=True,
                                                        scale=self.scale)
                del calc_so
            else:
                e_mk, v_knm = get_spinorbit_eigenvalues(self.calc,
                                                        return_wfs=True,
                                                        scale=self.scale)
            e_mk /= Hartree

        # Parallelization stuff
        nK = self.kd.nbzkpts
        myKrange, myKsize, mySsize = self.parallelisation_sizes()

        # Calculate exchange interaction
        qd0 = KPointDescriptor([self.q_c])
        pd0 = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd0)
        ikq_k = self.kd.find_k_plus_q(self.q_c)
        v_G = get_coulomb_kernel(pd0, self.kd.N_c, truncation=self.truncation,
                                 wstc=self.wstc)
        if optical:
            v_G[0] = 0.0

        self.pair = PairDensity(self.calc, self.ecut, world=serial_comm,
                                txt='pair.txt')

        # Calculate direct (screened) interaction and PAW corrections
        if self.mode == 'RPA':
            Q_aGii = self.pair.initialize_paw_corrections(pd0)
        else:
            self.get_screened_potential(ac=ac)
            if (self.qd.ibzk_kc - self.q_c < 1.0e-6).all():
                iq0 = self.qd.bz2ibz_k[self.kd.where_is_q(self.q_c,
                                                          self.qd.bzk_kc)]
                Q_aGii = self.Q_qaGii[iq0]
            else:
                Q_aGii = self.pair.initialize_paw_corrections(pd0)

        # Calculate pair densities, eigenvalues and occupations
        so = self.spinors + 1
        Nv, Nc = so * self.nv, so * self.nc
        Ns = self.spins
        rhoex_KsmnG = np.zeros((nK, Ns, Nv, Nc, len(v_G)), complex)
        # rhoG0_Ksmn = np.zeros((nK, Ns, Nv, Nc), complex)
        df_Ksmn = np.zeros((nK, Ns, Nv, Nc), float) # -(ev - ec)
        deps_ksmn = np.zeros((myKsize, Ns, Nv, Nc), float) # -(fv - fc)
        if np.allclose(self.q_c, 0.0):
            optical_limit = True
        else:
            optical_limit = False
        get_pair = self.pair.get_kpoint_pair
        get_rho = self.pair.get_pair_density
        if self.spinors:
            # Get all pair densities to allow for SOC mixing
            # Use twice as many no-SOC states as BSE bands to allow mixing
            vi_s = [2 * self.val_sn[0, 0] - self.val_sn[0, -1] - 1]
            vf_s = [2 * self.con_sn[0, -1] - self.con_sn[0, 0] + 2]
            if vi_s[0] < 0:
                vi_s[0] = 0
            ci_s, cf_s = vi_s, vf_s
            ni, nf = vi_s[0], vf_s[0]
            mvi = 2 * self.val_sn[0, 0]
            mvf = 2 * (self.val_sn[0, -1] + 1)
            mci = 2 * self.con_sn[0, 0]
            mcf = 2 * (self.con_sn[0, -1] + 1)
        else:
            vi_s, vf_s = self.val_sn[:, 0], self.val_sn[:, -1] + 1
            ci_s, cf_s = self.con_sn[:, 0], self.con_sn[:, -1] + 1
        for ik, iK in enumerate(myKrange):
            for s in range(Ns):
                pair = get_pair(pd0, s, iK,
                                vi_s[s], vf_s[s], ci_s[s], cf_s[s])
                m_m = np.arange(vi_s[s], vf_s[s])
                n_n = np.arange(ci_s[s], cf_s[s])
                if self.gw_skn is not None:
                    iKq = self.calc.wfs.kd.find_k_plus_q(self.q_c, [iK])[0]
                    epsv_m = self.gw_skn[s, iK, :self.nv]
                    epsc_n = self.gw_skn[s, iKq, self.nv:]
                    deps_ksmn[ik] = -(epsv_m[:, np.newaxis] - epsc_n)
                elif self.spinors:
                    iKq = self.calc.wfs.kd.find_k_plus_q(self.q_c, [iK])[0]
                    epsv_m = e_mk[mvi:mvf, iK]
                    epsc_n = e_mk[mci:mcf, iKq]
                    deps_ksmn[ik, s] = -(epsv_m[:, np.newaxis] - epsc_n)
                else:
                    deps_ksmn[ik, s] = -pair.get_transition_energies(m_m, n_n)

                df_mn = pair.get_occupation_differences(self.val_sn[s],
                                                        self.con_sn[s])
                rho_mnG = get_rho(pd0, pair,
                                  m_m, n_n,
                                  optical_limit=optical_limit,
                                  direction=self.direction,
                                  Q_aGii=Q_aGii,
                                  extend_head=False)
                if self.spinors:
                    if optical_limit:
                        deps0_mn = -pair.get_transition_energies(m_m, n_n)
                        rho_mnG[:, :, 0] *= deps0_mn
                    df_Ksmn[iK, s, ::2, ::2] = df_mn
                    df_Ksmn[iK, s, ::2, 1::2] = df_mn
                    df_Ksmn[iK, s, 1::2, ::2] = df_mn
                    df_Ksmn[iK, s, 1::2, 1::2] = df_mn
                    vecv0_nm = v_knm[iK][::2][ni:nf, mvi:mvf]
                    vecc0_nm = v_knm[iKq][::2][ni:nf, mci:mcf]
                    rho_0mnG = np.dot(vecv0_nm.T.conj(),
                                      np.dot(vecc0_nm.T, rho_mnG))
                    vecv1_nm = v_knm[iK][1::2][ni:nf, mvi:mvf]
                    vecc1_nm = v_knm[iKq][1::2][ni:nf, mci:mcf]
                    rho_1mnG = np.dot(vecv1_nm.T.conj(),
                                      np.dot(vecc1_nm.T, rho_mnG))
                    rhoex_KsmnG[iK, s] = rho_0mnG + rho_1mnG
                    if optical_limit:
                        rhoex_KsmnG[iK, s, :, :, 0] /= deps_ksmn[ik, s]
                else:
                    df_Ksmn[iK, s] = pair.get_occupation_differences(m_m, n_n)
                    rhoex_KsmnG[iK, s] = rho_mnG

        if self.eshift is not None:
            deps_ksmn[np.where(df_Ksmn[myKrange] > 1.0e-3)] += self.eshift
            deps_ksmn[np.where(df_Ksmn[myKrange] < -1.0e-3)] -= self.eshift

        world.sum(df_Ksmn)
        world.sum(rhoex_KsmnG)

        self.rhoG0_S = np.reshape(rhoex_KsmnG[:, :, :, :, 0], -1)

        if hasattr(self, 'H_sS'):
            return

        # Calculate Hamiltonian
        t0 = time()
        print('Calculating %s matrix elements at q_c = %s'
              % (self.mode, self.q_c), file=self.fd)
        H_ksmnKsmn = np.zeros((myKsize, Ns, Nv, Nc, nK, Ns, Nv, Nc), complex)
        for ik1, iK1 in enumerate(myKrange):
            for s1 in range(Ns):
                kptv1 = self.pair.get_k_point(s1, iK1, vi_s[s1], vf_s[s1])
                kptc1 = self.pair.get_k_point(s1, ikq_k[iK1], ci_s[s1],
                                              cf_s[s1])
                rho1_mnG = rhoex_KsmnG[iK1, s1]

                #rhoG0_Ksmn[iK1, s1] = rho1_mnG[:, :, 0]
                rho1ccV_mnG = rho1_mnG.conj()[:, :] * v_G
                for s2 in range(Ns):
                    for Q_c in self.qd.bzk_kc:
                        iK2 = self.kd.find_k_plus_q(Q_c, [kptv1.K])[0]
                        rho2_mnG = rhoex_KsmnG[iK2, s2]
                        rho2_mGn = np.swapaxes(rho2_mnG, 1, 2)
                        H_ksmnKsmn[ik1, s1, :, :, iK2, s2, :, :] += (
                            np.dot(rho1ccV_mnG, rho2_mGn))
                        if not self.mode == 'RPA' and s1 == s2:
                            ikq = ikq_k[iK2]
                            kptv2 = self.pair.get_k_point(s1, iK2, vi_s[s1],
                                                          vf_s[s1])
                            kptc2 = self.pair.get_k_point(s1, ikq, ci_s[s1],
                                                          cf_s[s1])
                            rho3_mmG, iq = self.get_density_matrix(kptv1,
                                                                   kptv2)
                            rho4_nnG, iq = self.get_density_matrix(kptc1,
                                                                   kptc2)
                            if self.spinors:
                                vec0_nm = v_knm[iK1][::2][ni:nf, mvi:mvf]
                                vec1_nm = v_knm[iK1][1::2][ni:nf, mvi:mvf]
                                vec2_nm = v_knm[iK2][::2][ni:nf, mvi:mvf]
                                vec3_nm = v_knm[iK2][1::2][ni:nf, mvi:mvf]
                                rho_0mnG = np.dot(vec0_nm.T.conj(),
                                                  np.dot(vec2_nm.T, rho3_mmG))
                                rho_1mnG = np.dot(vec1_nm.T.conj(),
                                                  np.dot(vec3_nm.T, rho3_mmG))
                                rho3_mmG = rho_0mnG + rho_1mnG
                                vec0_nm = v_knm[ikq_k[iK1]][::2][ni:nf, mci:mcf]
                                vec1_nm = v_knm[ikq_k[iK1]][1::2][ni:nf,mci:mcf]
                                vec2_nm = v_knm[ikq][::2][ni:nf, mci:mcf]
                                vec3_nm = v_knm[ikq][1::2][ni:nf, mci:mcf]
                                rho_0mnG = np.dot(vec0_nm.T.conj(),
                                                  np.dot(vec2_nm.T, rho4_nnG))
                                rho_1mnG = np.dot(vec1_nm.T.conj(),
                                                  np.dot(vec3_nm.T, rho4_nnG))
                                rho4_nnG = rho_0mnG + rho_1mnG

                            rho3ccW_mmG = np.dot(rho3_mmG.conj(),
                                                 self.W_qGG[iq])
                            W_mmnn = np.dot(rho3ccW_mmG,
                                            np.swapaxes(rho4_nnG, 1, 2))
                            W_mnmn = np.swapaxes(W_mmnn, 1, 2) * Ns * so
                            H_ksmnKsmn[ik1, s1, :, :, iK2, s1] -= 0.5 * W_mnmn
            if iK1 % (myKsize // 5 + 1) == 0:
                dt = time() - t0
                tleft = dt * myKsize / (iK1 + 1) - dt
                print('  Finished %s pair orbitals in %s - Estimated %s left' %
                      ((iK1 + 1) * Nv * Nc * Ns * world.size,
                       timedelta(seconds=round(dt)),
                       timedelta(seconds=round(tleft))), file=self.fd)

        #if self.mode == 'BSE':
        #    del self.Q_qaGii, self.W_qGG, self.pd_q

        H_ksmnKsmn /= self.vol

        mySsize = myKsize * Nv * Nc * Ns
        if myKsize > 0:
            iS0 = myKrange[0] *  Nv * Nc * Ns

        #world.sum(rhoG0_Ksmn)
        #self.rhoG0_S = np.reshape(rhoG0_Ksmn, -1)
        self.df_S = np.reshape(df_Ksmn, -1)
        if not self.td:
            self.excludef_S = np.where(np.abs(self.df_S) < 0.001)[0]
        # multiply by 2 when spin-paired and no SOC
        self.df_S *= 2.0 / nK / Ns / so
        self.deps_s = np.reshape(deps_ksmn, -1)
        H_sS = np.reshape(H_ksmnKsmn, (mySsize, self.nS))
        for iS in range(mySsize):
            # Multiply by occupations and adiabatic coupling
            H_sS[iS] *= self.df_S[iS0 + iS] * ac
            # add bare transition energies
            H_sS[iS, iS0 + iS] += self.deps_s[iS]

        self.H_sS = H_sS

        if self.write_h:
            self.par_save('H_SS.ulm', 'H_SS', self.H_sS)

    def get_density_matrix(self, kpt1, kpt2):

        Q_c = self.kd.bzk_kc[kpt2.K] - self.kd.bzk_kc[kpt1.K]
        iQ = self.qd.where_is_q(Q_c, self.qd.bzk_kc)
        iq = self.qd.bz2ibz_k[iQ]
        q_c = self.qd.ibzk_kc[iq]

        # Find symmetry that transforms Q_c into q_c
        sym = self.qd.sym_k[iQ]
        U_cc = self.qd.symmetry.op_scc[sym]
        time_reversal = self.qd.time_reversal_k[iQ]
        sign = 1 - 2 * time_reversal
        d_c = sign * np.dot(U_cc, q_c) - Q_c
        assert np.allclose(d_c.round(), d_c)

        pd = self.pd_q[iq]
        N_c = pd.gd.N_c
        i_cG = sign * np.dot(U_cc, np.unravel_index(pd.Q_qG[0], N_c))

        shift0_c = Q_c - sign * np.dot(U_cc, q_c)
        assert np.allclose(shift0_c.round(), shift0_c)
        shift0_c = shift0_c.round().astype(int)

        shift_c = kpt1.shift_c - kpt2.shift_c - shift0_c
        I_G = np.ravel_multi_index(i_cG + shift_c[:, None], N_c, 'wrap')
        G_Gv = pd.get_reciprocal_vectors()
        pos_ac = self.calc.spos_ac
        pos_av = np.dot(pos_ac, pd.gd.cell_cv)
        M_vv = np.dot(pd.gd.cell_cv.T, np.dot(U_cc.T,
                                              np.linalg.inv(pd.gd.cell_cv).T))

        Q_aGii = []
        for a, Q_Gii in enumerate(self.Q_qaGii[iq]):
            x_G = np.exp(1j * np.dot(G_Gv, (pos_av[a] -
                                            np.dot(M_vv, pos_av[a]))))
            U_ii = self.calc.wfs.setups[a].R_sii[sym]
            Q_Gii = np.dot(np.dot(U_ii, Q_Gii * x_G[:, None, None]),
                           U_ii.T).transpose(1, 0, 2)
            if sign == -1:
                Q_Gii = Q_Gii.conj()
            Q_aGii.append(Q_Gii)

        rho_mnG = np.zeros((len(kpt1.eps_n), len(kpt2.eps_n), len(G_Gv)),
                           complex)
        for m in range(len(rho_mnG)):
            C1_aGi = [np.dot(Qa_Gii, P1_ni[m].conj())
                      for Qa_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani)]
            ut1cc_R = kpt1.ut_nR[m].conj()
            rho_mnG[m] = self.pair.calculate_pair_densities(ut1cc_R, C1_aGi,
                                                            kpt2, pd, I_G)
        return rho_mnG, iq

    def get_screened_potential(self, ac=1.0):

        if hasattr(self, 'W_qGG'):
            return

        if self.wfile is not None:
            # Read screened potential from file
            try:
                data = np.load(self.wfile + '.npz')
                self.Q_qaGii = data['Q']
                self.W_qGG = data['W']
                self.pd_q = data['pd']
                print('Reading screened potential from % s' % self.wfile,
                      file=self.fd)
            except:
                self.calculate_screened_potential(ac)
                print('Saving screened potential to % s' % self.wfile,
                      file=self.fd)
                if world.rank == 0:
                    np.savez(self.wfile,
                             Q=self.Q_qaGii, pd=self.pd_q, W=self.W_qGG)
        else:
            self.calculate_screened_potential(ac)

    def calculate_screened_potential(self, ac):
        """Calculate W_GG(q)"""

        chi0 = Chi0(self.calc,
                    frequencies=[0.0],
                    eta=0.001,
                    ecut=self.ecut,
                    intraband=False,
                    hilbert=False,
                    nbands=self.nbands,
                    txt='chi0.txt',
                    world=world,
                    )

        self.blockcomm = chi0.blockcomm
        wfs = self.calc.wfs

        self.Q_qaGii = []
        self.W_qGG = []
        self.pd_q = []

        t0 = time()
        print('Calculating screened potential', file=self.fd)
        for iq, q_c in enumerate(self.qd.ibzk_kc):
            thisqd = KPointDescriptor([q_c])
            pd = PWDescriptor(self.ecut, wfs.gd, complex, thisqd)
            nG = pd.ngmax

            chi0.Ga = self.blockcomm.rank * nG
            chi0.Gb = min(chi0.Ga + nG, nG)
            chi0_wGG = np.zeros((1, nG, nG), complex)
            if np.allclose(q_c, 0.0):
                chi0_wxvG = np.zeros((1, 2, 3, nG), complex)
                chi0_wvv = np.zeros((1, 3, 3), complex)
            else:
                chi0_wxvG = None
                chi0_wvv = None

            chi0._calculate(pd, chi0_wGG, chi0_wxvG, chi0_wvv,
                            0, self.nbands, spins='all', extend_head=False)
            chi0_GG = chi0_wGG[0]

            # Calculate eps^{-1}_GG
            if pd.kd.gamma:
                # Generate fine grid in vicinity of gamma
                kd = self.calc.wfs.kd
                N = 4
                N_c = np.array([N, N, N])
                if self.truncation is not None:
                    # Only average periodic directions if trunction is used
                    N_c[kd.N_c == 1] = 1
                qf_qc = monkhorst_pack(N_c) / kd.N_c
                qf_qc *= 1.0e-6
                U_scc = kd.symmetry.op_scc
                qf_qc = kd.get_ibz_q_points(qf_qc, U_scc)[0]
                weight_q = kd.q_weights
                qf_qv = 2 * np.pi * np.dot(qf_qc, pd.gd.icell_cv)
                a_q = np.sum(np.dot(chi0_wvv[0], qf_qv.T) * qf_qv.T, axis=0)
                a0_qG = np.dot(qf_qv, chi0_wxvG[0, 0])
                a1_qG = np.dot(qf_qv, chi0_wxvG[0, 1])
                einv_GG = np.zeros((nG, nG), complex)
                # W_GG = np.zeros((nG, nG), complex)
                for iqf in range(len(qf_qv)):
                    chi0_GG[0] = a0_qG[iqf]
                    chi0_GG[:, 0] = a1_qG[iqf]
                    chi0_GG[0, 0] = a_q[iqf]
                    sqrV_G = get_coulomb_kernel(pd,
                                                kd.N_c,
                                                truncation=self.truncation,
                                                wstc=self.wstc,
                                                q_v=qf_qv[iqf])**0.5
                    sqrV_G *= ac**0.5  # Multiply by adiabatic coupling
                    e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:,
                                                                  np.newaxis]
                    einv_GG += np.linalg.inv(e_GG) * weight_q[iqf]
                    # einv_GG = np.linalg.inv(e_GG) * weight_q[iqf]
                    # W_GG += (einv_GG * sqrV_G * sqrV_G[:, np.newaxis]
                    #          * weight_q[iqf])
            else:
                sqrV_G = get_coulomb_kernel(pd,
                                            self.kd.N_c,
                                            truncation=self.truncation,
                                            wstc=self.wstc)**0.5
                sqrV_G *= ac**0.5  # Multiply by adiabatic coupling
                e_GG = np.eye(nG) - chi0_GG * sqrV_G * sqrV_G[:, np.newaxis]
                einv_GG = np.linalg.inv(e_GG)
                # W_GG = einv_GG * sqrV_G * sqrV_G[:, np.newaxis]

            # Now calculate W_GG
            if pd.kd.gamma:
                # Reset bare Coulomb interaction
                sqrV_G = get_coulomb_kernel(pd,
                                            self.kd.N_c,
                                            truncation=self.truncation,
                                            wstc=self.wstc)**0.5
            W_GG = einv_GG * sqrV_G * sqrV_G[:, np.newaxis]
            if self.integrate_gamma != 0:
                # Numerical integration of Coulomb interaction at all q-points
                if self.integrate_gamma == 2:
                    reduced = True
                else:
                    reduced = False
                V0, sqrV0 = get_integrated_kernel(pd,
                                                  self.kd.N_c,
                                                  truncation=self.truncation,
                                                  reduced=reduced,
                                                  N=100)
                W_GG[0, 0] = einv_GG[0, 0] * V0
                W_GG[0, 1:] = einv_GG[0, 1:] * sqrV0 * sqrV_G[1:]
                W_GG[1:, 0] = einv_GG[1:, 0] * sqrV_G[1:] * sqrV0
            elif self.integrate_gamma == 0 and pd.kd.gamma:
                # Analytical integration at gamma
                bzvol = (2 * np.pi)**3 / self.vol / self.qd.nbzkpts
                Rq0 = (3 * bzvol / (4 * np.pi))**(1. / 3.)
                V0 = 16 * np.pi**2 * Rq0 / bzvol
                sqrV0 = (4 * np.pi)**(1.5) * Rq0**2 / bzvol / 2
                W_GG[0, 0] = einv_GG[0, 0] * V0
                W_GG[0, 1:] = einv_GG[0, 1:] * sqrV0 * sqrV_G[1:]
                W_GG[1:, 0] = einv_GG[1:, 0] * sqrV_G[1:] * sqrV0
            else:
                pass

            if pd.kd.gamma:
                e = 1 / einv_GG[0, 0].real
                print('    RPA dielectric constant is: %3.3f' % e,
                      file=self.fd)
            self.Q_qaGii.append(chi0.Q_aGii)
            self.pd_q.append(pd)
            self.W_qGG.append(W_GG)

            if iq % (self.qd.nibzkpts // 5 + 1) == 2:
                dt = time() - t0
                tleft = dt * self.qd.nibzkpts / (iq + 1) - dt
                print('  Finished %s q-points in %s - Estimated %s left' %
                      (iq + 1, timedelta(seconds=round(dt)),
                       timedelta(seconds=round(tleft))), file=self.fd)

    def diagonalize(self):

        print('Diagonalizing Hamiltonian', file=self.fd)
        """The t and T represent local and global
           eigenstates indices respectively
        """

        # Non-Hermitian matrix can only use linalg.eig
        if not self.td:
            print('  Using numpy.linalg.eig...', file=self.fd)
            print('  Eliminated %s pair orbitals' % len(self.excludef_S),
                  file=self.fd)

            self.H_SS = self.collect_A_SS(self.H_sS)
            self.w_T = np.zeros(self.nS - len(self.excludef_S), complex)
            if world.rank == 0:
                self.H_SS = np.delete(self.H_SS, self.excludef_S, axis=0)
                self.H_SS = np.delete(self.H_SS, self.excludef_S, axis=1)
                self.w_T, self.v_ST = np.linalg.eig(self.H_SS)
            world.broadcast(self.w_T, 0)
            self.df_S = np.delete(self.df_S, self.excludef_S)
            self.rhoG0_S = np.delete(self.rhoG0_S, self.excludef_S)
        # Here the eigenvectors are returned as complex conjugated rows
        else:
            if world.size == 1:
                print('  Using lapack...', file=self.fd)
                from gpaw.utilities.lapack import diagonalize
                self.w_T = np.zeros(self.nS)
                diagonalize(self.H_sS, self.w_T)
                self.v_St = self.H_sS.conj().T
            else:
                print('  Using scalapack...', file=self.fd)
                nS = self.nS
                ns = -(-self.kd.nbzkpts // world.size) * (self.nv * self.nc *
                                                          self.spins *
                                                          (self.spinors + 1)**2)
                grid = BlacsGrid(world, world.size, 1)
                desc = grid.new_descriptor(nS, nS, ns, nS)

                desc2 = grid.new_descriptor(nS, nS, 2, 2)
                H_tmp = desc2.zeros(dtype=complex)
                r = Redistributor(world, desc, desc2)
                r.redistribute(self.H_sS, H_tmp)

                self.w_T = np.empty(nS)
                v_tmp = desc2.empty(dtype=complex)
                desc2.diagonalize_dc(H_tmp, v_tmp, self.w_T)

                r = Redistributor(grid.comm, desc2, desc)
                self.v_St = desc.zeros(dtype=complex)
                r.redistribute(v_tmp, self.v_St)
                self.v_St = self.v_St.conj().T

        if self.write_v and self.td:
            # Cannot use par_save without td
            self.par_save('v_TS.ulm', 'v_TS', self.v_St.T)

        return

    def get_bse_matrix(self, q_c=[0.0, 0.0, 0.0], direction=0, ac=1.0,
                       readfile=None, optical=True, write_eig=None):
        """Calculate and diagonalize BSE matrix"""

        self.q_c = q_c
        self.direction = direction

        if readfile is None:
            self.calculate(optical=optical, ac=ac)
            if hasattr(self, 'w_T'):
                return
            self.diagonalize()
        elif readfile == 'H_SS':
            print('Reading Hamiltonian from file', file=self.fd)
            self.par_load('H_SS.ulm', 'H_SS')
            self.diagonalize()
        elif readfile == 'v_TS':
            print('Reading eigenstates from file', file=self.fd)
            self.par_load('v_TS.ulm', 'v_TS')
        else:
            raise ValueError('%s array not recognized' % readfile)

        # TODO: Move write_eig here

        return

    def get_vchi(self, w_w=None, eta=0.1, q_c=[0.0, 0.0, 0.0],
                 direction=0, ac=1.0, readfile=None, optical=True,
                 write_eig=None):
        """Returns v * \chi where v is the bare Coulomb interaction"""

        self.get_bse_matrix(q_c=q_c, direction=direction, ac=ac,
                            readfile=readfile, optical=optical,
                            write_eig=write_eig)

        w_T = self.w_T
        rhoG0_S = self.rhoG0_S
        df_S = self.df_S

        print('Calculating response function at %s frequency points' %
              len(w_w), file=self.fd)
        vchi_w = np.zeros(len(w_w), dtype=complex)

        if not self.td:
            C_T = np.zeros(self.nS - len(self.excludef_S), complex)
            if world.rank == 0:
                A_T = np.dot(rhoG0_S, self.v_ST)
                B_T = np.dot(rhoG0_S * df_S, self.v_ST)
                tmp = np.dot(self.v_ST.conj().T, self.v_ST)
                overlap_tt = np.linalg.inv(tmp)
                C_T = np.dot(B_T.conj(), overlap_tt.T) * A_T
            world.broadcast(C_T, 0)
        else:
            A_t = np.dot(rhoG0_S, self.v_St)
            B_t = np.dot(rhoG0_S * df_S, self.v_St)
            if world.size == 1:
                C_T = B_t.conj() * A_t
            else:
                Nv = self.nv * (self.spinors + 1)
                Nc = self.nc * (self.spinors + 1)
                Ns = self.spins
                nS = self.nS
                ns = -(-self.kd.nbzkpts // world.size) * Nv * Nc * Ns
                grid = BlacsGrid(world, world.size, 1)
                desc = grid.new_descriptor(nS, 1, ns, 1)
                C_t = desc.empty(dtype=complex)
                C_t[:, 0] = B_t.conj() * A_t
                C_T = desc.collect_on_master(C_t)[:, 0]
                if world.rank != 0:
                    C_T = np.empty(nS, dtype=complex)
                world.broadcast(C_T, 0)

        eta /= Hartree
        for iw, w in enumerate(w_w / Hartree):
            tmp_T = 1. / (w - w_T + 1j * eta)
            vchi_w[iw] += np.dot(tmp_T, C_T)
        vchi_w *= 4 * np.pi / self.vol

        if not np.allclose(self.q_c, 0.0):
            cell_cv = self.calc.wfs.gd.cell_cv
            B_cv = 2 * np.pi * np.linalg.inv(cell_cv).T
            q_v = np.dot(q_c, B_cv)
            vchi_w /= np.dot(q_v, q_v)

        """Check f-sum rule."""
        nv = self.calc.wfs.setups.nvalence
        dw_w = (w_w[1:] - w_w[:-1]) / Hartree
        wchi_w = (w_w[1:] * vchi_w[1:] + w_w[:-1] * vchi_w[:-1]) / Hartree / 2
        N = -np.dot(dw_w, wchi_w.imag) * self.vol / (2 * np.pi**2)
        print(file=self.fd)
        print('Checking f-sum rule:', file=self.fd)
        print('  Valence = %s, N = %f' % (nv, N), file=self.fd)
        print(file=self.fd)

        if write_eig is not None:
            if world.rank == 0:
                f = open(write_eig, 'w')
                print('# %s eigenvalues in eV' % self.mode, file=f)
                for iw, w in enumerate(self.w_T * Hartree):
                    print('%8d %12.6f %12.16f' % (iw, w.real, C_T[iw].real),
                          file=f)
                f.close()

        return vchi_w * ac

    def get_dielectric_function(self, w_w=None, eta=0.1,
                                q_c=[0.0, 0.0, 0.0], direction=0,
                                filename='df_bse.csv', readfile=None,
                                write_eig='eig.dat'):
        """Returns and writes real and imaginary part of the dielectric
        function.

        w_w: list of frequencies (eV)
            Dielectric function is calculated at these frequencies
        eta: float
            Lorentzian broadening of the spectrum (eV)
        q_c: list of three floats
            Wavevector in reduced units on which the response is calculated
        direction: int
            if q_c = [0, 0, 0] this gives the direction in cartesian
            coordinates - 0=x, 1=y, 2=z
        filename: str
            data file on which frequencies, real and imaginary part of
            dielectric function is written
        readfile: str
            If H_SS is given, the method will load the BSE Hamiltonian
            from H_SS.ulm. If v_TS is given, the method will load the
            eigenstates from v_TS.ulm
        write_eig: str
            File on which the BSE eigenvalues are written
        """

        epsilon_w = -self.get_vchi(w_w=w_w, eta=eta, q_c=q_c,
                                   direction=direction,
                                   readfile=readfile, optical=True,
                                   write_eig=write_eig)
        epsilon_w += 1.0

        if world.rank == 0 and filename is not None:
            f = open(filename, 'w')
            for iw, w in enumerate(w_w):
                print('%.9f, %.9f, %.9f' %
                      (w, epsilon_w[iw].real, epsilon_w[iw].imag), file=f)
            f.close()
        world.barrier()

        print('Calculation completed at:', ctime(), file=self.fd)
        print(file=self.fd)

        return w_w, epsilon_w

    def get_eels_spectrum(self, w_w=None, eta=0.1,
                          q_c=[0.0, 0.0, 0.0], direction=0,
                          filename='df_bse.csv', readfile=None,
                          write_eig='eig.dat'):
        """Returns and writes real and imaginary part of the dielectric
        function.

        w_w: list of frequencies (eV)
            Dielectric function is calculated at these frequencies
        eta: float
            Lorentzian broadening of the spectrum (eV)
        q_c: list of three floats
            Wavevector in reduced units on which the response is calculated
        direction: int
            if q_c = [0, 0, 0] this gives the direction in cartesian
            coordinates - 0=x, 1=y, 2=z
        filename: str
            data file on which frequencies, real and imaginary part of
            dielectric function is written
        readfile: str
            If H_SS is given, the method will load the BSE Hamiltonian
            from H_SS.ulm. If v_TS is given, the method will load the
            eigenstates from v_TS.ulm
        write_eig: str
            File on which the BSE eigenvalues are written
        """

        eels_w = -self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction,
                                readfile=readfile, optical=False,
                                write_eig=write_eig).imag

        if world.rank == 0 and filename is not None:
            f = open(filename, 'w')
            for iw, w in enumerate(w_w):
                print('%.9f, %.9f' % (w, eels_w[iw]), file=f)
            f.close()
        world.barrier()

        print('Calculation completed at:', ctime(), file=self.fd)
        print(file=self.fd)

        return w_w, eels_w

    def get_polarizability(self, w_w=None, eta=0.1,
                           q_c=[0.0, 0.0, 0.0], direction=0,
                           filename='pol_bse.csv', readfile=None, pbc=None,
                           write_eig='eig.dat'):
        """Calculate the polarizability alpha.
        In 3D the imaginary part of the polarizability is related to the
        dielectric function by Im(eps_M) = 4 pi * Im(alpha). In systems
        with reduced dimensionality the converged value of alpha is
        independent of the cell volume. This is not the case for eps_M,
        which is ill defined. A truncated Coulomb kernel will always give
        eps_M = 1.0, whereas the polarizability maintains its structure.
        pbs should be a list of booleans giving the periodic directions.

        By default, generate a file 'pol_bse.csv'. The three colomns are:
        frequency (eV), Real(alpha), Imag(alpha). The dimension of alpha
        is \AA to the power of non-periodic directions.
        """

        cell_cv = self.calc.wfs.gd.cell_cv
        if not pbc:
            pbc_c = self.calc.atoms.pbc
        else:
            pbc_c = np.array(pbc)
        if pbc_c.all():
            V = 1.0
        else:
            V = np.abs(np.linalg.det(cell_cv[~pbc_c][:, ~pbc_c]))

        if self.truncation is None:
            optical = True
        else:
            optical = False

        vchi_w = self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction,
                               readfile=readfile, optical=optical,
                               write_eig=write_eig)
        alpha_w = -V * vchi_w / (4 * np.pi)
        alpha_w *= Bohr**(sum(~pbc_c))

        if world.rank == 0 and filename is not None:
            fd = open(filename, 'w')
            for iw, w in enumerate(w_w):
                print('%.9f, %.9f, %.9f' %
                      (w, alpha_w[iw].real, alpha_w[iw].imag), file=fd)
            fd.close()

        print('Calculation completed at:', ctime(), file=self.fd)
        print(file=self.fd)

        return w_w, alpha_w

    def get_2d_absorption(self, w_w=None, eta=0.1,
                          q_c=[0.0, 0.0, 0.0], direction=0,
                          filename='abs_bse.csv', readfile=None, pbc=None,
                          write_eig='eig.dat'):
        """Calculate the dimensionless absorption for 2d materials.
        It is essentially related to the 2D polarizability \alpha_2d as

              ABS = 4 * np.pi * \omega * \alpha_2d / c

        where c is the velocity of light
        """

        from ase.units import alpha
        c = 1.0 / alpha

        cell_cv = self.calc.wfs.gd.cell_cv
        if not pbc:
            pbc_c = self.calc.atoms.pbc
        else:
            pbc_c = np.array(pbc)

        assert np.sum(pbc_c) == 2
        V = np.abs(np.linalg.det(cell_cv[~pbc_c][:, ~pbc_c]))
        vchi_w = self.get_vchi(w_w=w_w, eta=eta, q_c=q_c, direction=direction,
                               readfile=readfile, optical=True,
                               write_eig=write_eig)
        abs_w = -V * vchi_w.imag * w_w / Hartree / c

        if world.rank == 0 and filename is not None:
            fd = open(filename, 'w')
            for iw, w in enumerate(w_w):
                print('%.9f, %.9f' % (w, abs_w[iw]), file=fd)
            fd.close()

        print('Calculation completed at:', ctime(), file=self.fd)
        print(file=self.fd)

        return w_w, abs_w

    def par_save(self, filename, name, A_sS):
        import ase.io.ulm as ulm

        if world.size == 1:
            A_XS = A_sS
        else:
            A_XS = self.collect_A_SS(A_sS)

        if world.rank == 0:
            w = ulm.open(filename, 'w')
            if name == 'v_TS':
                w.write(w_T=self.w_T)
            # w.write(nS=self.nS)
            w.write(rhoG0_S=self.rhoG0_S)
            w.write(df_S=self.df_S)
            w.write(A_XS=A_XS)
            w.close()
        world.barrier()

    def par_load(self, filename, name):
        import ase.io.ulm as ulm

        if world.rank == 0:
            r = ulm.open(filename, 'r')
            if name == 'v_TS':
                self.w_T = r.w_T
            self.rhoG0_S = r.rhoG0_S
            self.df_S = r.df_S
            A_XS = r.A_XS
            r.close()
        else:
            if name == 'v_TS':
                self.w_T = np.zeros((self.nS), dtype=float)
            self.rhoG0_S = np.zeros((self.nS), dtype=complex)
            self.df_S = np.zeros((self.nS), dtype=float)
            A_XS = None

        world.broadcast(self.rhoG0_S, 0)
        world.broadcast(self.df_S, 0)

        if name == 'H_SS':
            self.H_sS = self.distribute_A_SS(A_XS)

        if name == 'v_TS':
            world.broadcast(self.w_T, 0)
            self.v_St = self.distribute_A_SS(A_XS, transpose=True)

    def collect_A_SS(self, A_sS):
        if world.rank == 0:
            A_SS = np.zeros((self.nS, self.nS), dtype=complex)
            A_SS[:len(A_sS)] = A_sS
            Ntot = len(A_sS)
            for rank in range(1, world.size):
                nkr, nk, ns = self.parallelisation_sizes(rank)
                buf = np.empty((ns, self.nS), dtype=complex)
                world.receive(buf, rank, tag=123)
                A_SS[Ntot:Ntot + ns] = buf
                Ntot += ns
        else:
            world.send(A_sS, 0, tag=123)
        world.barrier()
        if world.rank == 0:
            return A_SS

    def distribute_A_SS(self, A_SS, transpose=False):
        if world.rank == 0:
            for rank in range(0, world.size):
                nkr, nk, ns = self.parallelisation_sizes(rank)
                if rank == 0:
                    A_sS = A_SS[0:ns]
                    Ntot = ns
                else:
                    world.send(A_SS[Ntot:Ntot + ns], rank, tag=123)
                    Ntot += ns
        else:
            nkr, nk, ns = self.parallelisation_sizes()
            A_sS = np.empty((ns, self.nS), dtype=complex)
            world.receive(A_sS, 0, tag=123)
        world.barrier()
        if transpose:
            A_sS = A_sS.T
        return A_sS

    def parallelisation_sizes(self, rank=None):
        if rank is None:
            rank = world.rank
        nK = self.kd.nbzkpts
        myKsize = -(-nK // world.size)
        myKrange = range(rank * myKsize,
                         min((rank + 1) * myKsize, nK))
        myKsize = len(myKrange)
        mySsize = myKsize * self.nv * self.nc * self.spins
        mySsize *= (1 + self.spinors)**2
        return myKrange, myKsize, mySsize

    def get_bse_wf(self):
        pass
        # asd = 1.0

    def print_initialization(self, td, eshift, gw_skn):
        p = functools.partial(print, file=self.fd)
        p('----------------------------------------------------------')
        p('%s Hamiltonian' % self.mode)
        p('----------------------------------------------------------')
        p('Started at:  ', ctime())
        p()
        p('Atoms                          :',
          self.calc.atoms.get_chemical_formula(mode='hill'))
        p('Ground state XC functional     :', self.calc.hamiltonian.xc.name)
        p('Valence electrons              :', self.calc.wfs.setups.nvalence)
        p('Spinor calculations            :', self.spinors)
        p('Number of bands                :', self.calc.wfs.bd.nbands)
        p('Number of spins                :', self.calc.wfs.nspins)
        p('Number of k-points             :', self.kd.nbzkpts)
        p('Number of irreducible k-points :', self.kd.nibzkpts)
        p('Number of q-points             :', self.qd.nbzkpts)
        p('Number of irreducible q-points :', self.qd.nibzkpts)
        p()
        for q in self.qd.ibzk_kc:
            p('    q: [%1.4f %1.4f %1.4f]' % (q[0], q[1], q[2]))
        p()
        if gw_skn is not None:
            p('User specified BSE bands')
        p('Response PW cutoff             :', self.ecut * Hartree, 'eV')
        p('Screening bands included       :', self.nbands)
        if len(self.val_sn) == 1:
            p('Valence bands                  :', self.val_sn[0])
            p('Conduction bands               :', self.con_sn[0])
        else:
            p('Valence bands                  :', self.val_sn[0],self.val_sn[1])
            p('Conduction bands               :', self.con_sn[0],self.con_sn[1])
        if eshift is not None:
            p('Scissors operator              :', eshift * Hartree, 'eV')
        p('Tamm-Dancoff approximation     :', td)
        p('Number of pair orbitals        :', self.nS)
        p()
        p('Truncation of Coulomb kernel   :', self.truncation)
        if self.integrate_gamma == 0:
            p('Coulomb integration scheme     :', 'Analytical - gamma only')
        elif self.integrate_gamma == 1:
            p('Coulomb integration scheme     :', 'Numerical - all q-points')
        else:
            pass
        p()
        p('----------------------------------------------------------')
        p('----------------------------------------------------------')
        p()
        p('Parallelization - Total number of CPUs   : % s' % world.size)
        p('  Screened potential')
        p('    K-point/band decomposition           : % s' % world.size)
        p('  Hamiltonian')
        p('    Pair orbital decomposition           : % s' % world.size)
        p()
Exemple #15
0
class UTKPointParallelSetup(TestCase):
    """
    Setup a simple kpoint parallel calculation."""

    # Number of bands
    nbands = 1

    # Spin-polarized
    nspins = 1

    # Mean spacing and number of grid points per axis (G x G x G)
    h = 0.25 / Bohr
    G = 48

    ## Symmetry-reduction of k-points TODO
    #symmetry = p.usesymm #XXX 'None' is an allowed value!!!

    # Whether spin/k-points are equally distributed (determines nibzkpts)
    equipartition = None
    nibzkpts = None

    gamma = False # can't be gamma point when nibzkpts > 1 ...
    dtype = complex #XXX virtual so far..

    # =================================

    def setUp(self):
        for virtvar in ['equipartition']:
            assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar

        kpts = {'even' : (12,1,2), \
                'prime': (23,1,1)}[self.equipartition]

        #primes = [i for i in xrange(50,1,-1) if ~np.any(i%np.arange(2,i)==0)]
        bzk_kc = kpts2ndarray(kpts)
        assert p.usesymm == None
        self.nibzkpts = len(bzk_kc)

        #parsize, parsize_bands = create_parsize_minbands(self.nbands, world.size)
        parsize, parsize_bands = 1, 1 #XXX
        assert self.nbands % np.prod(parsize_bands) == 0
        domain_comm, kpt_comm, band_comm = distribute_cpus(parsize,
            parsize_bands, self.nspins, self.nibzkpts)

        # Set up band descriptor:
        self.bd = BandDescriptor(self.nbands, band_comm, p.parallel['stridebands'])

        # Set up grid descriptor:
        res, ngpts = shapeopt(300, self.G**3, 3, 0.2)
        cell_c = self.h * np.array(ngpts)
        pbc_c = (True, False, True)
        self.gd = GridDescriptor(ngpts, cell_c, pbc_c, domain_comm, parsize)

        # Create randomized gas-like atomic configuration
        self.atoms = create_random_atoms(self.gd)

        # Create setups
        Z_a = self.atoms.get_atomic_numbers()
        self.setups = Setups(Z_a, p.setups, p.basis, p.lmax, xc)
        self.natoms = len(self.setups)

        # Set up kpoint descriptor:
        self.kd = KPointDescriptor(bzk_kc, self.nspins)
        self.kd.set_symmetry(self.atoms, self.setups, p.usesymm)
        self.kd.set_communicator(kpt_comm)

    def tearDown(self):
        del self.bd, self.gd, self.kd
        del self.setups, self.atoms

    def get_parsizes(self): #XXX NO LONGER IN UT_HSOPS?!?
        # Careful, overwriting imported GPAW params may cause amnesia in Python.
        from gpaw import parsize, parsize_bands

        # Choose the largest possible parallelization over kpoint/spins
        test_parsize_ks_pairs = gcd(self.nspins*self.nibzkpts, world.size)
        remsize = world.size//test_parsize_ks_pairs

        # If parsize_bands is not set, choose the largest possible
        test_parsize_bands = parsize_bands or gcd(self.nbands, remsize)

        # If parsize_bands is not set, choose as few domains as possible
        test_parsize = parsize or (remsize//test_parsize_bands)

        return test_parsize, test_parsize_bands

    # =================================

    def verify_comm_sizes(self): #TODO needs work
        if world.size == 1:
            return
        comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \
                                                   self.gd.comm, self.kd.comm]])
        self._parinfo =  '%d world, %d band, %d domain, %d kpt' % comm_sizes
        #self.assertEqual((self.nspins*self.nibzkpts) % self.kd.comm.size, 0) #XXX

    def verify_slice_consistency(self):
        for kpt_rank in range(self.kd.comm.size):
            uslice = self.kd.get_slice(kpt_rank)
            myus = np.arange(*uslice.indices(self.kd.nks))
            for myu,u in enumerate(myus):
                self.assertEqual(self.kd.who_has(u), (kpt_rank, myu))

    def verify_combination_consistency(self):
        for u in range(self.kd.nks):
            s, k = self.kd.what_is(u)
            self.assertEqual(self.kd.where_is(s, k), u)

        for s in range(self.kd.nspins):
            for k in range(self.kd.nibzkpts):
                u = self.kd.where_is(s, k)
                self.assertEqual(self.kd.what_is(u), (s,k,))

    def verify_indexing_consistency(self):
        for u in range(self.kd.nks):
            kpt_rank, myu = self.kd.who_has(u)
            self.assertEqual(self.kd.global_index(myu, kpt_rank), u)

        for kpt_rank in range(self.kd.comm.size):
            for myu in range(self.kd.get_count(kpt_rank)):
                u = self.kd.global_index(myu, kpt_rank)
                self.assertEqual(self.kd.who_has(u), (kpt_rank, myu))

    def verify_ranking_consistency(self):
        ranks = self.kd.get_ranks()

        for kpt_rank in range(self.kd.comm.size):
            my_indices = self.kd.get_indices(kpt_rank)
            matches = np.argwhere(ranks == kpt_rank).ravel()
            self.assertTrue((matches == my_indices).all())
            for myu in range(self.kd.get_count(kpt_rank)):
                u = self.kd.global_index(myu, kpt_rank)
                self.assertEqual(my_indices[myu], u)
Exemple #16
0
class G0W0(PairDensity):
    def __init__(self, calc, filename='gw',
                 kpts=None, bands=None, nbands=None, ppa=False,
                 wstc=False,
                 ecut=150.0, eta=0.1, E0=1.0 * Hartree,
                 domega0=0.025, omega2=10.0,
                 world=mpi.world):
    
        PairDensity.__init__(self, calc, ecut, world=world,
                             txt=filename + '.txt')
        
        self.filename = filename
        
        ecut /= Hartree
        
        self.ppa = ppa
        self.wstc = wstc
        self.eta = eta / Hartree
        self.E0 = E0 / Hartree
        self.domega0 = domega0 / Hartree
        self.omega2 = omega2 / Hartree

        print('  ___  _ _ _ ', file=self.fd)
        print(' |   || | | |', file=self.fd)
        print(' | | || | | |', file=self.fd)
        print(' |__ ||_____|', file=self.fd)
        print(' |___|       ', file=self.fd)
        print(file=self.fd)

        self.kpts = select_kpts(kpts, self.calc)
                
        if bands is None:
            bands = [0, self.nocc2]
            
        self.bands = bands

        b1, b2 = bands
        self.shape = shape = (self.calc.wfs.nspins, len(self.kpts), b2 - b1)
        self.eps_sin = np.empty(shape)     # KS-eigenvalues
        self.f_sin = np.empty(shape)       # occupation numbers
        self.sigma_sin = np.zeros(shape)   # self-energies
        self.dsigma_sin = np.zeros(shape)  # derivatives of self-energies
        self.vxc_sin = None                # KS XC-contributions
        self.exx_sin = None                # exact exchange contributions
        self.Z_sin = None                  # renormalization factors
        
        if nbands is None:
            nbands = int(self.vol * ecut**1.5 * 2**0.5 / 3 / pi**2)
        self.nbands = nbands

        kd = self.calc.wfs.kd

        self.mysKn1n2 = None  # my (s, K, n1, n2) indices
        self.distribute_k_points_and_bands(b1, b2, kd.ibz2bz_k[self.kpts])
        
        # Find q-vectors and weights in the IBZ:
        assert -1 not in kd.bz2bz_ks
        offset_c = 0.5 * ((kd.N_c + 1) % 2) / kd.N_c
        bzq_qc = monkhorst_pack(kd.N_c) + offset_c
        self.qd = KPointDescriptor(bzq_qc)
        self.qd.set_symmetry(self.calc.atoms, self.calc.wfs.setups,
                             usesymm=self.calc.input_parameters.usesymm,
                             N_c=self.calc.wfs.gd.N_c)
        
        assert self.calc.wfs.nspins == 1
        
    @timer('G0W0')
    def calculate(self):
        kd = self.calc.wfs.kd

        self.calculate_ks_xc_contribution()
        self.calculate_exact_exchange()

        # Get KS eigenvalues and occupation numbers:
        b1, b2 = self.bands
        for i, k in enumerate(self.kpts):
            kpt = self.calc.wfs.kpt_u[k]
            self.eps_sin[0, i] = kpt.eps_n[b1:b2]
            self.f_sin[0, i] = kpt.f_n[b1:b2] / kpt.weight

        # My part of the states we want to calculate QP-energies for:
        mykpts = [self.get_k_point(s, K, n1, n2)
                  for s, K, n1, n2 in self.mysKn1n2]
        
        # Loop over q in the IBZ:
        for pd0, W0, q_c in self.calculate_screened_potential():
            for kpt1 in mykpts:
                K2 = kd.find_k_plus_q(q_c, [kpt1.K])[0]
                kpt2 = self.get_k_point(0, K2, 0, self.nbands)
                k1 = kd.bz2ibz_k[kpt1.K]
                i = self.kpts.index(k1)
                self.calculate_q(i, kpt1, kpt2, pd0, W0)

        self.world.sum(self.sigma_sin)
        self.world.sum(self.dsigma_sin)
        
        self.Z_sin = 1 / (1 - self.dsigma_sin)
        self.qp_sin = self.eps_sin + self.Z_sin * (self.sigma_sin +
                                                   self.exx_sin -
                                                   self.vxc_sin)
        
        results = {'f': self.f_sin,
                   'eps': self.eps_sin * Hartree,
                   'vxc': self.vxc_sin * Hartree,
                   'exx': self.exx_sin * Hartree,
                   'sigma': self.sigma_sin * Hartree,
                   'Z': self.Z_sin,
                   'qp': self.qp_sin * Hartree}
      
        self.print_results(results)
        
        return results
        
    def calculate_q(self, i, kpt1, kpt2, pd0, W0):
        wfs = self.calc.wfs
        
        N_c = pd0.gd.N_c
        i_cG = self.sign * np.dot(self.U_cc,
                                  np.unravel_index(pd0.Q_qG[0], N_c))

        q_c = wfs.kd.bzk_kc[kpt2.K] - wfs.kd.bzk_kc[kpt1.K]
        q0 = np.allclose(q_c, 0)

        shift0_c = q_c - self.sign * np.dot(self.U_cc, pd0.kd.bzk_kc[0])
        assert np.allclose(shift0_c.round(), shift0_c)
        shift0_c = shift0_c.round().astype(int)

        shift_c = kpt1.shift_c - kpt2.shift_c - shift0_c
        I_G = np.ravel_multi_index(i_cG + shift_c[:, None], N_c, 'wrap')
        
        G_Gv = pd0.G_Qv[pd0.Q_qG[0]] + pd0.K_qv[0]
        pos_av = np.dot(self.spos_ac, pd0.gd.cell_cv)
        M_vv = np.dot(pd0.gd.cell_cv.T,
                      np.dot(self.U_cc.T,
                             np.linalg.inv(pd0.gd.cell_cv).T))
        
        Q_aGii = []
        for a, Q_Gii in enumerate(self.Q_aGii):
            x_G = np.exp(1j * np.dot(G_Gv, (pos_av[a] -
                                            self.sign *
                                            np.dot(M_vv, pos_av[a]))))
            U_ii = self.calc.wfs.setups[a].R_sii[self.s]
            Q_Gii = np.dot(np.dot(U_ii, Q_Gii * x_G[:, None, None]),
                           U_ii.T).transpose(1, 0, 2)
            Q_aGii.append(Q_Gii)

        if debug:
            self.check(i_cG, shift0_c, N_c, q_c, Q_aGii)
                
        if self.ppa:
            calculate_sigma = self.calculate_sigma_ppa
        else:
            calculate_sigma = self.calculate_sigma
            
        for n in range(kpt1.n2 - kpt1.n1):
            ut1cc_R = kpt1.ut_nR[n].conj()
            eps1 = kpt1.eps_n[n]
            C1_aGi = [np.dot(Q_Gii, P1_ni[n].conj())
                      for Q_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani)]
            n_mG = self.calculate_pair_densities(ut1cc_R, C1_aGi, kpt2,
                                                 pd0, I_G)
            if self.sign == 1:
                n_mG = n_mG.conj()
                
            if q0:
                n_mG[:, 0] = 0
                m = n + kpt1.n1 - kpt2.n1
                if 0 <= m < len(n_mG):
                    n_mG[m, 0] = 1.0
                    
            f_m = kpt2.f_n
            deps_m = eps1 - kpt2.eps_n
            sigma, dsigma = calculate_sigma(n_mG, deps_m, f_m, W0)
            nn = kpt1.n1 + n - self.bands[0]
            self.sigma_sin[kpt1.s, i, nn] += sigma
            self.dsigma_sin[kpt1.s, i, nn] += dsigma
            
    def check(self, i_cG, shift0_c, N_c, q_c, Q_aGii):
        I0_G = np.ravel_multi_index(i_cG - shift0_c[:, None], N_c, 'wrap')
        qd1 = KPointDescriptor([q_c])
        pd1 = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd1)
        G_I = np.empty(N_c.prod(), int)
        G_I[:] = -1
        I1_G = pd1.Q_qG[0]
        G_I[I1_G] = np.arange(len(I0_G))
        G_G = G_I[I0_G]
        assert len(I0_G) == len(I1_G)
        assert (G_G >= 0).all()

        for a, Q_Gii in enumerate(self.initialize_paw_corrections(pd1)):
            e = abs(Q_aGii[a] - Q_Gii[G_G]).max()
            assert e < 1e-12

    @timer('Sigma')
    def calculate_sigma(self, n_mG, deps_m, f_m, C_swGG):
        o_m = abs(deps_m)
        # Add small number to avoid zeros for degenerate states:
        sgn_m = np.sign(deps_m + 1e-15)
        
        # Pick +i*eta or -i*eta:
        s_m = (1 + sgn_m * np.sign(0.5 - f_m)).astype(int) // 2
        
        beta = (2**0.5 - 1) * self.domega0 / self.omega2
        w_m = (o_m / (self.domega0 + beta * o_m)).astype(int)
        o1_m = self.omega_w[w_m]
        o2_m = self.omega_w[w_m + 1]
        
        x = 1.0 / (self.qd.nbzkpts * 2 * pi * self.vol)
        sigma = 0.0
        dsigma = 0.0
        for o, o1, o2, sgn, s, w, n_G in zip(o_m, o1_m, o2_m,
                                             sgn_m, s_m, w_m, n_mG):
            C1_GG = C_swGG[s][w]
            C2_GG = C_swGG[s][w + 1]
            p = x * sgn
            sigma1 = p * np.dot(np.dot(n_G, C1_GG), n_G.conj()).imag
            sigma2 = p * np.dot(np.dot(n_G, C2_GG), n_G.conj()).imag
            sigma += ((o - o1) * sigma2 + (o2 - o) * sigma1) / (o2 - o1)
            dsigma += sgn * (sigma2 - sigma1) / (o2 - o1)
            
        return sigma, dsigma

    @timer('W')
    def calculate_screened_potential(self):
        print('Calulating screened Coulomb potential', file=self.fd)
            
        if self.ppa:
            print('Using Godby-Needs plasmon-pole approximation:',
                  file=self.fd)
            print('    Fitting energy: i*E0, E0 = %.3f Hartee' % self.E0,
                  file=self.fd)

            # use small imaginary frequency to avoid dividing by zero:
            frequencies = [1e-10j, 1j * self.E0 * Hartree]
            
            parameters = {'eta': 0,
                          'hilbert': False,
                          'timeordered': False,
                          'frequencies': frequencies}
        else:
            parameters = {'eta': self.eta * Hartree,
                          'hilbert': True,
                          'timeordered': True,
                          'domega0': self.domega0 * Hartree,
                          'omega2': self.omega2 * Hartree}
            
        chi0 = Chi0(self.calc,
                    nbands=self.nbands,
                    ecut=self.ecut * Hartree,
                    intraband=False,
                    real_space_derivatives=False,
                    txt=self.filename + '.w.txt',
                    timer=self.timer,
                    **parameters)
        
        self.omega_w = chi0.omega_w
        self.omegamax = chi0.omegamax
        
        htp = HilbertTransform(self.omega_w, self.eta, gw=True)
        htm = HilbertTransform(self.omega_w, -self.eta, gw=True)
            
        for iq, q_c in enumerate(self.qd.ibzk_kc):
            if self.wstc:
                wstc = WignerSeitzTruncatedCoulomb(
                    self.calc.wfs.gd.cell_cv,
                    self.calc.wfs.kd.N_c,
                    chi0.fd)
            else:
                wstc = None
                
            pd, chi0_wGG = chi0.calculate(q_c)[:2]
            self.Q_aGii = chi0.Q_aGii
            W = self.calculate_w(pd, chi0_wGG, q_c, htp, htm, wstc)
            
            Q1 = self.qd.ibz2bz_k[iq]
            done = set()
            for s, Q2 in enumerate(self.qd.bz2bz_ks[Q1]):
                if Q2 >= 0 and Q2 not in done:
                    s = self.qd.sym_k[Q2]
                    self.s = s
                    self.U_cc = self.qd.symmetry.op_scc[s]
                    time_reversal = self.qd.time_reversal_k[Q2]
                    self.sign = 1 - 2 * time_reversal
                    Q_c = self.qd.bzk_kc[Q2]
                    d_c = self.sign * np.dot(self.U_cc, q_c) - Q_c
                    assert np.allclose(d_c.round(), d_c)
                    yield pd, W, Q_c
                    done.add(Q2)
    
    @timer('WW')
    def calculate_w(self, pd, chi0_wGG, q_c, htp, htm, wstc):
        if self.wstc:
            iG_G = (wstc.get_potential(pd) / (4 * pi))**0.5
            if np.allclose(q_c, 0):
                chi0_wGG[:, 0] = 0.0
                chi0_wGG[:, :, 0] = 0.0
                G0inv = 0.0
                G20inv = 0.0
            else:
                G0inv = None
                G20inv = None
        else:
            if np.allclose(q_c, 0):
                dq3 = (2 * pi)**3 / (self.qd.nbzkpts * self.vol)
                qc = (dq3 / 4 / pi * 3)**(1 / 3)
                G0inv = 2 * pi * qc**2 / dq3
                G20inv = 4 * pi * qc / dq3
                G_G = pd.G2_qG[0]**0.5
                G_G[0] = 1
                iG_G = 1 / G_G
            else:
                iG_G = pd.G2_qG[0]**-0.5
                G0inv = None
                G20inv = None

        delta_GG = np.eye(len(iG_G))

        if self.ppa:
            return self.ppa_w(chi0_wGG, iG_G, delta_GG, G0inv, G20inv, q_c)
            
        self.timer.start('Dyson eq.')
        # Calculate W and store it in chi0_wGG ndarray:
        for chi0_GG in chi0_wGG:
            e_GG = (delta_GG -
                    4 * pi * chi0_GG * iG_G * iG_G[:, np.newaxis])
            W_GG = chi0_GG
            W_GG[:] = 4 * pi * (np.linalg.inv(e_GG) -
                                delta_GG) * iG_G * iG_G[:, np.newaxis]
            if np.allclose(q_c, 0):
                W_GG[0, 0] *= G20inv
                W_GG[1:, 0] *= G0inv
                W_GG[0, 1:] *= G0inv
                
        Wp_wGG = chi0_wGG.copy()
        Wm_wGG = chi0_wGG
        with self.timer('Hilbert transform'):
            htp(Wp_wGG)
            htm(Wm_wGG)
        self.timer.stop('Dyson eq.')
        
        return [Wp_wGG, Wm_wGG]

    @timer('Kohn-Sham XC-contribution')
    def calculate_ks_xc_contribution(self):
        name = self.filename + '.vxc.npy'
        fd = opencew(name)
        if fd is None:
            print('Reading Kohn-Sham XC contribution from file:', name,
                  file=self.fd)
            with open(name) as fd:
                self.vxc_sin = np.load(fd)
            assert self.vxc_sin.shape == self.shape, self.vxc_sin.shape
            return
            
        print('Calculating Kohn-Sham XC contribution', file=self.fd)
        vxc_skn = vxc(self.calc, self.calc.hamiltonian.xc) / Hartree
        n1, n2 = self.bands
        self.vxc_sin = vxc_skn[:, self.kpts, n1:n2]
        np.save(fd, self.vxc_sin)
        
    @timer('EXX')
    def calculate_exact_exchange(self):
        name = self.filename + '.exx.npy'
        fd = opencew(name)
        if fd is None:
            print('Reading EXX contribution from file:', name, file=self.fd)
            with open(name) as fd:
                self.exx_sin = np.load(fd)
            assert self.exx_sin.shape == self.shape, self.exx_sin.shape
            return
            
        print('Calculating EXX contribution', file=self.fd)
        exx = EXX(self.calc, kpts=self.kpts, bands=self.bands,
                  txt=self.filename + '.exx.txt', timer=self.timer)
        exx.calculate()
        self.exx_sin = exx.get_eigenvalue_contributions() / Hartree
        np.save(fd, self.exx_sin)

    def print_results(self, results):
        description = ['f:     Occupation numbers',
                       'eps:   KS-eigenvalues [eV]',
                       'vxc:   KS vxc [eV]',
                       'exx:   Exact exchange [eV]',
                       'sigma: Self-energies [eV]',
                       'Z:     Renormalization factors',
                       'qp:    QP-energies [eV]']

        print('\nResults:', file=self.fd)
        for line in description:
            print(line, file=self.fd)
            
        b1, b2 = self.bands
        names = [line.split(':', 1)[0] for line in description]
        ibzk_kc = self.calc.wfs.kd.ibzk_kc
        for s in range(self.calc.wfs.nspins):
            for i, ik in enumerate(self.kpts):
                print('\nk-point ' +
                      '{0} ({1}): ({2:.3f}, {3:.3f}, {4:.3f})'.format(
                          i, ik, *ibzk_kc[ik]), file=self.fd)
                print('band' +
                      ''.join('{0:>8}'.format(name) for name in names),
                      file=self.fd)
                for n in range(b2 - b1):
                    print('{0:4}'.format(n + b1) +
                          ''.join('{0:8.3f}'.format(results[name][s, i, n])
                                  for name in names),
                          file=self.fd)

        self.timer.write(self.fd)

    @timer('PPA')
    def ppa_w(self, chi0_wGG, iG_G, delta_GG, G0inv, G20inv, q_c):
        einv_wGG = []
        for chi0_GG in chi0_wGG:
            e_GG = (delta_GG -
                    4 * pi * chi0_GG * iG_G * iG_G[:, np.newaxis])
            einv_wGG.append(np.linalg.inv(e_GG) - delta_GG)

        if self.wstc and np.allclose(q_c, 0):
            einv_wGG[0][0] = 42
            einv_wGG[0][:, 0] = 42
        omegat_GG = self.E0 * np.sqrt(einv_wGG[1] /
                                      (einv_wGG[0] - einv_wGG[1]))
        R_GG = -0.5 * omegat_GG * einv_wGG[0]
        W_GG = 4 * pi**2 * R_GG * iG_G * iG_G[:, np.newaxis]
        if np.allclose(q_c, 0):
            W_GG[0, 0] *= G20inv
            W_GG[1:, 0] *= G0inv
            W_GG[0, 1:] *= G0inv

        return [W_GG, omegat_GG]
        
    @timer('PPA-Sigma')
    def calculate_sigma_ppa(self, n_mG, deps_m, f_m, W):
        W_GG, omegat_GG = W
        deps_mGG = deps_m[:, np.newaxis, np.newaxis]
        sign_mGG = 2 * f_m[:, np.newaxis, np.newaxis] - 1
        x1_mGG = 1 / (deps_mGG + omegat_GG - 1j * self.eta)
        x2_mGG = 1 / (deps_mGG - omegat_GG + 1j * self.eta)
        x3_mGG = 1 / (deps_mGG + omegat_GG - 1j * self.eta * sign_mGG)
        x4_mGG = 1 / (deps_mGG - omegat_GG - 1j * self.eta * sign_mGG)
        x_mGG = W_GG * (sign_mGG * (x1_mGG - x2_mGG) + x3_mGG + x4_mGG)
        dx_mGG = W_GG * (sign_mGG * (x1_mGG**2 - x2_mGG**2) +
                         x3_mGG**2 + x4_mGG**2)

        sigma = 0.0
        dsigma = 0.0
        for m in range(np.shape(n_mG)[0]):
            nW_mG = np.dot(n_mG[m], x_mGG[m])
            sigma += np.vdot(n_mG[m], nW_mG).real
            nW_mG = np.dot(n_mG[m], dx_mGG[m])
            dsigma -= np.vdot(n_mG[m], nW_mG).real
        
        x = 1 / (self.qd.nbzkpts * 2 * pi * self.vol)
        return x * sigma, x * dsigma
Exemple #17
0
    def initialize(self, atoms=None):
        """Inexpensive initialization."""

        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        par = self.input_parameters

        world = par.communicator
        if world is None:
            world = mpi.world
        elif hasattr(world, 'new_communicator'):
            # Check for whether object has correct type already
            #
            # Using isinstance() is complicated because of all the
            # combinations, serial/parallel/debug...
            pass
        else:
            # world should be a list of ranks:
            world = mpi.world.new_communicator(np.asarray(world))
        self.wfs.world = world

        if 'txt' in self._changed_keywords:
            self.set_txt(par.txt)
        self.verbose = par.verbose

        natoms = len(atoms)

        cell_cv = atoms.get_cell() / Bohr
        pbc_c = atoms.get_pbc()
        Z_a = atoms.get_atomic_numbers()
        magmom_av = atoms.get_initial_magnetic_moments()

        self.check_atoms()

        # Generate new xc functional only when it is reset by set
        # XXX sounds like this should use the _changed_keywords dictionary.
        if self.hamiltonian is None or self.hamiltonian.xc is None:
            if isinstance(par.xc, str):
                xc = XC(par.xc)
            else:
                xc = par.xc
        else:
            xc = self.hamiltonian.xc

        mode = par.mode

        if mode == 'fd':
            mode = FD()
        elif mode == 'pw':
            mode = pw.PW()
        elif mode == 'lcao':
            mode = LCAO()
        else:
            assert hasattr(mode, 'name'), str(mode)

        if xc.orbital_dependent and mode.name == 'lcao':
            raise NotImplementedError('LCAO mode does not support '
                                      'orbital-dependent XC functionals.')

        if par.realspace is None:
            realspace = (mode.name != 'pw')
        else:
            realspace = par.realspace
            if mode.name == 'pw':
                assert not realspace

        if par.filter is None and mode.name != 'pw':
            gamma = 1.6
            if par.gpts is not None:
                h = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5 / par.gpts).max()
            else:
                h = (par.h or 0.2) / Bohr

            def filter(rgd, rcut, f_r, l=0):
                gcut = np.pi / h - 2 / rcut / gamma
                f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l)
        else:
            filter = par.filter

        setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc, filter,
                        world)

        if magmom_av.ndim == 1:
            collinear = True
            magmom_av, magmom_a = np.zeros((natoms, 3)), magmom_av
            magmom_av[:, 2] = magmom_a
        else:
            collinear = False

        magnetic = magmom_av.any()

        spinpol = par.spinpol
        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True
            magmom_av[0] = (0, 0, setups[0].get_hunds_rule_moment(par.charge))

        if spinpol is None:
            spinpol = magnetic
        elif magnetic and not spinpol:
            raise ValueError('Non-zero initial magnetic moment for a ' +
                             'spin-paired calculation!')

        if collinear:
            nspins = 1 + int(spinpol)
            ncomp = 1
        else:
            nspins = 1
            ncomp = 2

        if par.usesymm != 'default':
            warnings.warn('Use "symmetry" keyword instead of ' +
                          '"usesymm" keyword')
            par.symmetry = usesymm2symmetry(par.usesymm)

        symm = par.symmetry
        if symm == 'off':
            symm = {'point_group': False, 'time_reversal': False}

        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kd = KPointDescriptor(bzkpts_kc, nspins, collinear)
        m_av = magmom_av.round(decimals=3)  # round off
        id_a = zip(setups.id_a, *m_av.T)
        symmetry = Symmetry(id_a, cell_cv, atoms.pbc, **symm)
        kd.set_symmetry(atoms, symmetry, comm=world)
        setups.set_symmetry(symmetry)

        if par.gpts is not None:
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace,
                                            kd.symmetry)

        symmetry.check_grid(N_c)

        width = par.width
        if width is None:
            if pbc_c.any():
                width = 0.1  # eV
            else:
                width = 0.0
        else:
            assert par.occupations is None

        if hasattr(self, 'time') or par.dtype == complex:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        nao = setups.nao
        nvalence = setups.nvalence - par.charge
        M_v = magmom_av.sum(0)
        M = np.dot(M_v, M_v)**0.5

        nbands = par.nbands

        orbital_free = any(setup.orbital_free for setup in setups)
        if orbital_free:
            nbands = 1

        if isinstance(nbands, basestring):
            if nbands[-1] == '%':
                basebands = int(nvalence + M + 0.5) // 2
                nbands = int((float(nbands[:-1]) / 100) * basebands)
            else:
                raise ValueError('Integer Expected: Only use a string '
                                 'if giving a percentage of occupied bands')

        if nbands is None:
            nbands = 0
            for setup in setups:
                nbands_from_atom = setup.get_default_nbands()

                # Any obscure setup errors?
                if nbands_from_atom < -(-setup.Nv // 2):
                    raise ValueError('Bad setup: This setup requests %d'
                                     ' bands but has %d electrons.' %
                                     (nbands_from_atom, setup.Nv))
                nbands += nbands_from_atom
            nbands = min(nao, nbands)
        elif nbands > nao and mode.name == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: '
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        if nbands <= 0:
            nbands = int(nvalence + M + 0.5) // 2 + (-nbands)

        if nvalence > 2 * nbands and not orbital_free:
            raise ValueError('Too few bands!  Electrons: %f, bands: %d' %
                             (nvalence, nbands))

        nbands *= ncomp

        if par.width is not None:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width).')
        if par.fixmom:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width, fixmagmom=True).')

        if self.occupations is None:
            if par.occupations is None:
                # Create object for occupation numbers:
                if orbital_free:
                    width = 0.0  # even for PBC
                    self.occupations = occupations.TFOccupations(
                        width, par.fixmom)
                else:
                    self.occupations = occupations.FermiDirac(
                        width, par.fixmom)
            else:
                self.occupations = par.occupations

            # If occupation numbers are changed, and we have wave functions,
            # recalculate the occupation numbers
            if self.wfs is not None and not isinstance(self.wfs,
                                                       EmptyWaveFunctions):
                self.occupations.calculate(self.wfs)

        self.occupations.magmom = M_v[2]

        cc = par.convergence

        if mode.name == 'lcao':
            niter_fixdensity = 0
        else:
            niter_fixdensity = None

        if self.scf is None:
            force_crit = cc['forces']
            if force_crit is not None:
                force_crit /= Hartree / Bohr
            self.scf = SCFLoop(cc['eigenstates'] / Hartree**2 * nvalence,
                               cc['energy'] / Hartree * max(nvalence, 1),
                               cc['density'] * nvalence, par.maxiter,
                               par.fixdensity, niter_fixdensity, force_crit)

        parsize_kpt = par.parallel['kpt']
        parsize_domain = par.parallel['domain']
        parsize_bands = par.parallel['band']

        if not realspace:
            pbc_c = np.ones(3, bool)

        if not self.wfs:
            if parsize_domain == 'domain only':  # XXX this was silly!
                parsize_domain = world.size

            parallelization = mpi.Parallelization(world, nspins * kd.nibzkpts)
            ndomains = None
            if parsize_domain is not None:
                ndomains = np.prod(parsize_domain)
            if mode.name == 'pw':
                if ndomains > 1:
                    raise ValueError('Planewave mode does not support '
                                     'domain decomposition.')
                ndomains = 1
            parallelization.set(kpt=parsize_kpt,
                                domain=ndomains,
                                band=parsize_bands)
            comms = parallelization.build_communicators()
            domain_comm = comms['d']
            kpt_comm = comms['k']
            band_comm = comms['b']
            kptband_comm = comms['D']
            domainband_comm = comms['K']

            self.comms = comms
            kd.set_communicator(kpt_comm)

            parstride_bands = par.parallel['stridebands']

            # Unfortunately we need to remember that we adjusted the
            # number of bands so we can print a warning if it differs
            # from the number specified by the user.  (The number can
            # be inferred from the input parameters, but it's tricky
            # because we allow negative numbers)
            self.nbands_parallelization_adjustment = -nbands % band_comm.size
            nbands += self.nbands_parallelization_adjustment

            # I would like to give the following error message, but apparently
            # there are cases, e.g. gpaw/test/gw_ppa.py, which involve
            # nbands > nao and are supposed to work that way.
            #if nbands > nao:
            #    raise ValueError('Number of bands %d adjusted for band '
            #                     'parallelization %d exceeds number of atomic '
            #                     'orbitals %d.  This problem can be fixed '
            #                     'by reducing the number of bands a bit.'
            #                     % (nbands, band_comm.size, nao))
            bd = BandDescriptor(nbands, band_comm, parstride_bands)

            if (self.density is not None
                    and self.density.gd.comm.size != domain_comm.size):
                # Domain decomposition has changed, so we need to
                # reinitialize density and hamiltonian:
                if par.fixdensity:
                    raise RuntimeError(
                        'Density reinitialization conflict ' +
                        'with "fixdensity" - specify domain decomposition.')
                self.density = None
                self.hamiltonian = None

            # Construct grid descriptor for coarse grids for wave functions:
            gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c, domain_comm,
                                            parsize_domain)

            # do k-point analysis here? XXX
            args = (gd, nvalence, setups, bd, dtype, world, kd, kptband_comm,
                    self.timer)

            if par.parallel['sl_auto']:
                # Choose scalapack parallelization automatically

                for key, val in par.parallel.items():
                    if (key.startswith('sl_') and key != 'sl_auto'
                            and val is not None):
                        raise ValueError("Cannot use 'sl_auto' together "
                                         "with '%s'" % key)
                max_scalapack_cpus = bd.comm.size * gd.comm.size
                nprow = max_scalapack_cpus
                npcol = 1

                # Get a sort of reasonable number of columns/rows
                while npcol < nprow and nprow % 2 == 0:
                    npcol *= 2
                    nprow //= 2
                assert npcol * nprow == max_scalapack_cpus

                # ScaLAPACK creates trouble if there aren't at least a few
                # whole blocks; choose block size so there will always be
                # several blocks.  This will crash for small test systems,
                # but so will ScaLAPACK in any case
                blocksize = min(-(-nbands // 4), 64)
                sl_default = (nprow, npcol, blocksize)
            else:
                sl_default = par.parallel['sl_default']

            if mode.name == 'lcao':
                # Layouts used for general diagonalizer
                sl_lcao = par.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                lcaoksl = get_KohnSham_layouts(sl_lcao,
                                               'lcao',
                                               gd,
                                               bd,
                                               domainband_comm,
                                               dtype,
                                               nao=nao,
                                               timer=self.timer)

                self.wfs = mode(collinear, lcaoksl, *args)

            elif mode.name == 'fd' or mode.name == 'pw':
                # buffer_size keyword only relevant for fdpw
                buffer_size = par.parallel['buffer_size']
                # Layouts used for diagonalizer
                sl_diagonalize = par.parallel['sl_diagonalize']
                if sl_diagonalize is None:
                    sl_diagonalize = sl_default
                diagksl = get_KohnSham_layouts(
                    sl_diagonalize,
                    'fd',  # XXX
                    # choice of key 'fd' not so nice
                    gd,
                    bd,
                    domainband_comm,
                    dtype,
                    buffer_size=buffer_size,
                    timer=self.timer)

                # Layouts used for orthonormalizer
                sl_inverse_cholesky = par.parallel['sl_inverse_cholesky']
                if sl_inverse_cholesky is None:
                    sl_inverse_cholesky = sl_default
                if sl_inverse_cholesky != sl_diagonalize:
                    message = 'sl_inverse_cholesky != sl_diagonalize ' \
                        'is not implemented.'
                    raise NotImplementedError(message)
                orthoksl = get_KohnSham_layouts(sl_inverse_cholesky,
                                                'fd',
                                                gd,
                                                bd,
                                                domainband_comm,
                                                dtype,
                                                buffer_size=buffer_size,
                                                timer=self.timer)

                # Use (at most) all available LCAO for initialization
                lcaonbands = min(nbands, nao)

                try:
                    lcaobd = BandDescriptor(lcaonbands, band_comm,
                                            parstride_bands)
                except RuntimeError:
                    initksl = None
                else:
                    # Layouts used for general diagonalizer
                    # (LCAO initialization)
                    sl_lcao = par.parallel['sl_lcao']
                    if sl_lcao is None:
                        sl_lcao = sl_default
                    initksl = get_KohnSham_layouts(sl_lcao,
                                                   'lcao',
                                                   gd,
                                                   lcaobd,
                                                   domainband_comm,
                                                   dtype,
                                                   nao=nao,
                                                   timer=self.timer)

                if hasattr(self, 'time'):
                    assert mode.name == 'fd'
                    from gpaw.tddft import TimeDependentWaveFunctions
                    self.wfs = TimeDependentWaveFunctions(
                        par.stencils[0], diagksl, orthoksl, initksl, gd,
                        nvalence, setups, bd, world, kd, kptband_comm,
                        self.timer)
                elif mode.name == 'fd':
                    self.wfs = mode(par.stencils[0], diagksl, orthoksl,
                                    initksl, *args)
                else:
                    assert mode.name == 'pw'
                    self.wfs = mode(diagksl, orthoksl, initksl, *args)
            else:
                self.wfs = mode(self, *args)
        else:
            self.wfs.set_setups(setups)

        if not self.wfs.eigensolver:
            # Number of bands to converge:
            nbands_converge = cc['bands']
            if nbands_converge == 'all':
                nbands_converge = nbands
            elif nbands_converge != 'occupied':
                assert isinstance(nbands_converge, int)
                if nbands_converge < 0:
                    nbands_converge += nbands
            eigensolver = get_eigensolver(par.eigensolver, mode,
                                          par.convergence)
            eigensolver.nbands_converge = nbands_converge
            # XXX Eigensolver class doesn't define an nbands_converge property

            if isinstance(xc, SIC):
                eigensolver.blocksize = 1
            self.wfs.set_eigensolver(eigensolver)

        if self.density is None:
            gd = self.wfs.gd
            if par.stencils[1] != 9:
                # Construct grid descriptor for fine grids for densities
                # and potentials:
                finegd = gd.refine()
            else:
                # Special case (use only coarse grid):
                finegd = gd

            if realspace:
                self.density = RealSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear, par.stencils[1])
            else:
                self.density = pw.ReciprocalSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear)

        self.density.initialize(setups, self.timer, magmom_av, par.hund)
        self.density.set_mixer(par.mixer)

        if self.hamiltonian is None:
            gd, finegd = self.density.gd, self.density.finegd
            if realspace:
                self.hamiltonian = RealSpaceHamiltonian(
                    gd, finegd, nspins, setups, self.timer, xc, world,
                    self.wfs.kptband_comm, par.external, collinear,
                    par.poissonsolver, par.stencils[1])
            else:
                self.hamiltonian = pw.ReciprocalSpaceHamiltonian(
                    gd, finegd, self.density.pd2, self.density.pd3, nspins,
                    setups, self.timer, xc, world, self.wfs.kptband_comm,
                    par.external, collinear)

        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)

        self.text()
        self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth)
        self.txt.flush()

        self.timer.print_info(self)

        if dry_run:
            self.dry_run()

        if realspace and \
                self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT':
            self.hamiltonian.poisson.set_density(self.density)
            self.hamiltonian.poisson.print_messages(self.text)
            self.txt.flush()

        self.initialized = True
        self._changed_keywords.clear()
Exemple #18
0
class GWQEHCorrection(PairDensity):
    def __init__(self,
                 calc,
                 gwfile,
                 filename=None,
                 kpts=[0],
                 bands=None,
                 structure=None,
                 d=None,
                 layer=0,
                 dW_qw=None,
                 qqeh=None,
                 wqeh=None,
                 txt=sys.stdout,
                 world=mpi.world,
                 domega0=0.025,
                 omega2=10.0,
                 eta=0.1,
                 include_q0=True,
                 metal=False):
        """ 
        Class for calculating quasiparticle energies of van der Waals
        heterostructures using the GW approximation for the self-energy. 
        The quasiparticle energy correction due to increased screening from
        surrounding layers is obtained from the QEH model.
        Parameters:
        
        calc: str or PAW object
            GPAW calculator object or filename of saved calculator object.
        gwfile: str
            name of gw results file from the monolayer calculation
        filename: str
            filename for gwqeh output
        kpts: list
            List of indices of sthe IBZ k-points to calculate the quasi
            particle energies for. Set to [0] by default since the QP 
            correction is generally the same for all k. 
        bands: tuple
            Range of band indices, like (n1, n2+1), to calculate the quasi
            particle energies for. Note that the second band index is not
            included. Should be the same as used for the GW calculation.
        structure: list of str
            Heterostructure set up. Each entry should consist of number of
            layers + chemical formula.
            For example: ['3H-MoS2', graphene', '10H-WS2'] gives 3 layers of
            H-MoS2, 1 layer of graphene and 10 layers of H-WS2.
            The name of the layers should correspond to building block files:
            "<name>-chi.pckl" in the local repository. 
        d: array of floats
            Interlayer distances for neighboring layers in Ang.
            Length of array = number of layers - 1
        layer: int
            index of layer to calculate QP correction for. 
        dW_qw: 2D array of floats dimension q X w
            Change in screened interaction. Should be set to None to calculate 
            dW directly from buildingblocks.
        qqeh: array of floats
            q-grid used for dW_qw (only needed if dW is given by hand).
        wqeh: array of floats
            w-grid used for dW_qw. So far this have to be the same as for the 
            GWQEH calculation.  (only needed if dW is given by hand).
        domega0: float
            Minimum frequency step (in eV) used in the generation of the non-
            linear frequency grid.
        omega2: float
            Control parameter for the non-linear frequency grid, equal to the
            frequency where the grid spacing has doubled in size.
        eta: float
            Broadening parameter.
        include_q0: bool
            include q=0 in W or not. if True an integral arround q=0 is
            performed, if False the q=0 contribution is set to zero. 
        metal: bool
            If True, the point at q=0 is omitted when averaging the screened
            potential close to q=0. 
        """

        self.gwfile = gwfile

        self.inputcalc = calc
        # Set low ecut in order to use PairDensity object since only
        # G=0 is needed.
        self.ecut = 1.
        PairDensity.__init__(self,
                             calc,
                             ecut=self.ecut,
                             world=world,
                             txt=filename + '.txt')

        self.filename = filename
        self.ecut /= Hartree
        self.eta = eta / Hartree
        self.domega0 = domega0 / Hartree
        self.omega2 = omega2 / Hartree

        self.kpts = list(select_kpts(kpts, self.calc))

        if bands is None:
            bands = [0, self.nocc2]

        self.bands = bands

        b1, b2 = bands
        self.shape = shape = (self.calc.wfs.nspins, len(self.kpts), b2 - b1)
        self.eps_sin = np.empty(shape)  # KS-eigenvalues
        self.f_sin = np.empty(shape)  # occupation numbers
        self.sigma_sin = np.zeros(shape)  # self-energies
        self.dsigma_sin = np.zeros(shape)  # derivatives of self-energies
        self.Z_sin = None  # renormalization factors
        self.qp_sin = None
        self.Qp_sin = None

        self.ecutnb = 150 / Hartree
        vol = abs(np.linalg.det(self.calc.wfs.gd.cell_cv))
        self.vol = vol
        self.nbands = min(self.calc.get_number_of_bands(),
                          int(vol * (self.ecutnb)**1.5 * 2**0.5 / 3 / pi**2))

        self.nspins = self.calc.wfs.nspins

        kd = self.calc.wfs.kd

        self.mysKn1n2 = None  # my (s, K, n1, n2) indices
        self.distribute_k_points_and_bands(b1, b2, kd.ibz2bz_k[self.kpts])

        # Find q-vectors and weights in the IBZ:
        assert -1 not in kd.bz2bz_ks
        offset_c = 0.5 * ((kd.N_c + 1) % 2) / kd.N_c
        bzq_qc = monkhorst_pack(kd.N_c) + offset_c
        self.qd = KPointDescriptor(bzq_qc)
        self.qd.set_symmetry(self.calc.atoms, kd.symmetry)

        # frequency grid
        omax = self.find_maximum_frequency()
        self.omega_w = frequency_grid(self.domega0, self.omega2, omax)
        self.nw = len(self.omega_w)
        self.wsize = 2 * self.nw

        # Calculate screened potential of Heterostructure
        if dW_qw is None:
            try:
                self.qqeh, self.wqeh, dW_qw = pickle.load(
                    open(filename + '_dW_qw.pckl', 'rb'))
            except:
                dW_qw = self.calculate_W_QEH(structure, d, layer)
        else:
            self.qqeh = qqeh
            self.wqeh = None  # wqeh

        self.dW_qw = self.get_W_on_grid(dW_qw,
                                        include_q0=include_q0,
                                        metal=metal)

        assert self.nw == self.dW_qw.shape[1], \
            ('Frequency grids doesnt match!')

        self.htp = HilbertTransform(self.omega_w, self.eta, gw=True)
        self.htm = HilbertTransform(self.omega_w, -self.eta, gw=True)

        self.complete = False
        self.nq = 0
        if self.load_state_file():
            if self.complete:
                print('Self-energy loaded from file', file=self.fd)

    def calculate_QEH(self):
        print('Calculating QEH self-energy contribution', file=self.fd)

        kd = self.calc.wfs.kd

        # Reset calculation
        self.sigma_sin = np.zeros(self.shape)  # self-energies
        self.dsigma_sin = np.zeros(self.shape)  # derivatives of self-energies

        # Get KS eigenvalues and occupation numbers:
        b1, b2 = self.bands
        nibzk = self.calc.wfs.kd.nibzkpts
        for i, k in enumerate(self.kpts):
            for s in range(self.nspins):
                u = s * nibzk + k
                kpt = self.calc.wfs.kpt_u[u]
                self.eps_sin[s, i] = kpt.eps_n[b1:b2]
                self.f_sin[s, i] = kpt.f_n[b1:b2] / kpt.weight

        # My part of the states we want to calculate QP-energies for:
        mykpts = [
            self.get_k_point(s, K, n1, n2) for s, K, n1, n2 in self.mysKn1n2
        ]

        kplusqdone_u = [set() for kpt in mykpts]
        Nq = len((self.qd.ibzk_kc))
        for iq, q_c in enumerate(self.qd.ibzk_kc):
            self.nq = iq
            nq = iq
            self.save_state_file()

            qcstr = '(' + ', '.join(['%.3f' % x for x in q_c]) + ')'
            print('Calculating contribution from IBZ q-point #%d/%d q_c=%s' %
                  (nq, Nq, qcstr),
                  file=self.fd)

            rcell_cv = 2 * pi * np.linalg.inv(self.calc.wfs.gd.cell_cv).T
            q_abs = np.linalg.norm(np.dot(q_c, rcell_cv))

            # Screened potential
            dW_w = self.dW_qw[nq]
            dW_w = dW_w[:, np.newaxis, np.newaxis]
            L = abs(self.calc.wfs.gd.cell_cv[2, 2])
            dW_w *= L

            nw = self.nw

            Wpm_w = np.zeros([2 * nw, 1, 1], dtype=complex)
            Wpm_w[:nw] = dW_w
            Wpm_w[nw:] = Wpm_w[0:nw]

            with self.timer('Hilbert transform'):
                self.htp(Wpm_w[:nw])
                self.htm(Wpm_w[nw:])

            qd = KPointDescriptor([q_c])
            pd0 = PWDescriptor(self.ecut, self.calc.wfs.gd, complex, qd)

            # modify pd0 by hand - only G=0 component is needed
            pd0.G_Qv = np.array([1e-17, 1e-17, 1e-17])[np.newaxis, :]
            pd0.Q_qG = [np.array([0], dtype='int32')]
            pd0.ngmax = 1
            G_Gv = pd0.get_reciprocal_vectors()

            self.Q_aGii = self.initialize_paw_corrections(pd0)

            # Loop over all k-points in the BZ and find those that are related
            # to the current IBZ k-point by symmetry
            Q1 = self.qd.ibz2bz_k[iq]
            Q2s = set()
            for s, Q2 in enumerate(self.qd.bz2bz_ks[Q1]):
                if Q2 >= 0 and Q2 not in Q2s:
                    Q2s.add(Q2)

            for Q2 in Q2s:
                s = self.qd.sym_k[Q2]
                self.s = s
                U_cc = self.qd.symmetry.op_scc[s]
                time_reversal = self.qd.time_reversal_k[Q2]
                self.sign = 1 - 2 * time_reversal
                Q_c = self.qd.bzk_kc[Q2]
                d_c = self.sign * np.dot(U_cc, q_c) - Q_c
                assert np.allclose(d_c.round(), d_c)

                for u1, kpt1 in enumerate(mykpts):
                    K2 = kd.find_k_plus_q(Q_c, [kpt1.K])[0]
                    kpt2 = self.get_k_point(kpt1.s,
                                            K2,
                                            0,
                                            self.nbands,
                                            block=True)
                    k1 = kd.bz2ibz_k[kpt1.K]
                    i = self.kpts.index(k1)

                    N_c = pd0.gd.N_c
                    i_cG = self.sign * np.dot(
                        U_cc, np.unravel_index(pd0.Q_qG[0], N_c))

                    k1_c = kd.bzk_kc[kpt1.K]
                    k2_c = kd.bzk_kc[K2]
                    # This is the q that connects K1 and K2 in the 1st BZ
                    q1_c = kd.bzk_kc[K2] - kd.bzk_kc[kpt1.K]

                    # G-vector that connects the full Q_c with q1_c
                    shift1_c = q1_c - self.sign * np.dot(U_cc, q_c)
                    assert np.allclose(shift1_c.round(), shift1_c)
                    shift1_c = shift1_c.round().astype(int)
                    shift_c = kpt1.shift_c - kpt2.shift_c - shift1_c
                    I_G = np.ravel_multi_index(i_cG + shift_c[:, None], N_c,
                                               'wrap')
                    pos_av = np.dot(self.spos_ac, pd0.gd.cell_cv)
                    M_vv = np.dot(
                        pd0.gd.cell_cv.T,
                        np.dot(U_cc.T,
                               np.linalg.inv(pd0.gd.cell_cv).T))
                    Q_aGii = []
                    for a, Q_Gii in enumerate(self.Q_aGii):
                        x_G = np.exp(
                            1j * np.dot(G_Gv,
                                        (pos_av[a] - np.dot(M_vv, pos_av[a]))))
                        U_ii = self.calc.wfs.setups[a].R_sii[self.s]
                        Q_Gii = np.dot(
                            np.dot(U_ii, Q_Gii * x_G[:, None, None]),
                            U_ii.T).transpose(1, 0, 2)
                        if self.sign == -1:
                            Q_Gii = Q_Gii.conj()
                        Q_aGii.append(Q_Gii)

                    for n in range(kpt1.n2 - kpt1.n1):
                        ut1cc_R = kpt1.ut_nR[n].conj()
                        eps1 = kpt1.eps_n[n]
                        C1_aGi = [
                            np.dot(Qa_Gii, P1_ni[n].conj())
                            for Qa_Gii, P1_ni in zip(Q_aGii, kpt1.P_ani)
                        ]

                        n_mG = self.calculate_pair_densities(
                            ut1cc_R, C1_aGi, kpt2, pd0, I_G)
                        if self.sign == 1:
                            n_mG = n_mG.conj()

                        f_m = kpt2.f_n
                        deps_m = eps1 - kpt2.eps_n
                        sigma, dsigma = self.calculate_sigma(
                            n_mG, deps_m, f_m, Wpm_w)
                        nn = kpt1.n1 + n - self.bands[0]
                        self.sigma_sin[kpt1.s, i, nn] += sigma
                        self.dsigma_sin[kpt1.s, i, nn] += dsigma

        self.world.sum(self.sigma_sin)
        self.world.sum(self.dsigma_sin)

        self.complete = True
        self.save_state_file()

        return self.sigma_sin, self.dsigma_sin

    def calculate_qp_correction(self):
        if self.filename:
            pckl = self.filename + '_qeh.pckl'
        else:
            pckl = 'qeh.pckl'

        if self.complete:
            print('Self-energy loaded from file', file=self.fd)
        else:
            self.calculate_QEH()

        # Need GW result for renormalization factor
        b1, b2 = self.bands
        gwdata = pickle.load(open(self.gwfile, 'rb'))
        self.dsigmagw_sin = gwdata['dsigma']
        self.qpgw_sin = gwdata['qp'] / Hartree
        nk = self.qpgw_sin.shape[1]
        if not self.sigma_sin.shape[1] == nk:
            self.sigma_sin = np.repeat(self.sigma_sin[:, :1, :], nk, axis=1)
            self.dsigma_sin = np.repeat(self.dsigma_sin[:, :1, :], nk, axis=1)
        self.Z_sin = 1. / (1 - self.dsigma_sin - self.dsigmagw_sin)
        self.qp_sin = self.Z_sin * self.sigma_sin

        return self.qp_sin * Hartree

    def calculate_qp_energies(self):
        # calculate
        qp_sin = self.calculate_qp_correction() / Hartree
        self.Qp_sin = self.qpgw_sin + qp_sin
        self.save_state_file()
        return self.Qp_sin * Hartree

    @timer('Sigma')
    def calculate_sigma(self, n_mG, deps_m, f_m, W_wGG):
        """Calculates a contribution to the self-energy and its derivative for
        a given (k, k-q)-pair from its corresponding pair-density and
        energy."""
        o_m = abs(deps_m)
        # Add small number to avoid zeros for degenerate states:
        sgn_m = np.sign(deps_m + 1e-15)

        # Pick +i*eta or -i*eta:
        s_m = (1 + sgn_m * np.sign(0.5 - f_m)).astype(int) // 2
        world = self.world
        comm = self.blockcomm
        nw = len(self.omega_w)
        nG = n_mG.shape[1]
        mynG = (nG + comm.size - 1) // comm.size
        Ga = min(comm.rank * mynG, nG)
        Gb = min(Ga + mynG, nG)
        beta = (2**0.5 - 1) * self.domega0 / self.omega2
        w_m = (o_m / (self.domega0 + beta * o_m)).astype(int)
        o1_m = self.omega_w[w_m]
        o2_m = self.omega_w[w_m + 1]

        x = 1.0 / (self.qd.nbzkpts * 2 * pi * self.vol)
        sigma = 0.0
        dsigma = 0.0
        # Performing frequency integration
        for o, o1, o2, sgn, s, w, n_G in zip(o_m, o1_m, o2_m, sgn_m, s_m, w_m,
                                             n_mG):

            C1_GG = W_wGG[s * nw + w]
            C2_GG = W_wGG[s * nw + w + 1]
            p = x * sgn
            myn_G = n_G[Ga:Gb]
            sigma1 = p * np.dot(np.dot(myn_G, C1_GG), n_G.conj()).imag
            sigma2 = p * np.dot(np.dot(myn_G, C2_GG), n_G.conj()).imag
            sigma += ((o - o1) * sigma2 + (o2 - o) * sigma1) / (o2 - o1)
            dsigma += sgn * (sigma2 - sigma1) / (o2 - o1)

        return sigma, dsigma

    def save_state_file(self, q=0):
        data = {
            'kpts': self.kpts,
            'bands': self.bands,
            'nbands': self.nbands,
            'last_q': self.nq,
            'complete': self.complete,
            'sigma_sin': self.sigma_sin,
            'dsigma_sin': self.dsigma_sin,
            'qp_sin': self.qp_sin,
            'Qp_sin': self.Qp_sin
        }
        if self.world.rank == 0:
            with open(self.filename + '_qeh.pckl', 'wb') as fd:
                pickle.dump(data, fd)

    def load_state_file(self):
        try:
            data = pickle.load(open(self.filename + '_qeh.pckl', 'rb'))
        except IOError:
            return False
        else:
            if (data['kpts'] == self.kpts and data['bands'] == self.bands
                    and data['nbands'] == self.nbands):
                self.nq = data['last_q']
                self.complete = data['complete']
                self.complete = data['complete']
                self.sigma_sin = data['sigma_sin']
                self.dsigma_sin = data['dsigma_sin']
                return True
            else:
                return False

    def get_W_on_grid(self, dW_qw, include_q0=True, metal=False):
        """This function transforms the screened potential W(q,w) to the 
        (q,w)-grid of the GW calculation. Also, W is integrated over
        a region around q=0 if include_q0 is set to True."""

        q_cs = self.qd.ibzk_kc

        rcell_cv = 2 * pi * np.linalg.inv(self.calc.wfs.gd.cell_cv).T
        q_vs = np.dot(q_cs, rcell_cv)
        q_grid = (q_vs**2).sum(axis=1)**0.5
        self.q_grid = q_grid
        w_grid = self.omega_w

        wqeh = self.wqeh  # w_grid.copy() # self.qeh
        qqeh = self.qqeh
        sortqeh = np.argsort(qqeh)
        qqeh = qqeh[sortqeh]
        dW_qw = dW_qw[sortqeh]

        sort = np.argsort(q_grid)
        isort = np.argsort(sort)
        if metal and np.isclose(qqeh[0], 0):
            """We don't have the right q=0 limit for metals  and semi-metals.
            -> Point should be omitted from interpolation"""
            qqeh = qqeh[1:]
            dW_qw = dW_qw[1:]
            sort = sort[1:]

        from scipy.interpolate import RectBivariateSpline
        yr = RectBivariateSpline(qqeh, wqeh, dW_qw.real, s=0)
        yi = RectBivariateSpline(qqeh, wqeh, dW_qw.imag, s=0)

        dWgw_qw = yr(q_grid[sort], w_grid) + 1j * yi(q_grid[sort], w_grid)
        dW_qw = yr(qqeh, w_grid) + 1j * yi(qqeh, w_grid)

        if metal:
            # Interpolation is done -> put back zeros at q=0
            dWgw_qw = np.insert(dWgw_qw, 0, 0, axis=0)
            qqeh = np.insert(qqeh, 0, 0)
            dW_qw = np.insert(dW_qw, 0, 0, axis=0)
            q_cut = q_grid[sort][0] / 2.
        else:
            q_cut = q_grid[sort][1] / 2.

        q0 = np.array([q for q in qqeh if q <= q_cut])
        if len(q0) > 1:  # Integrate arround q=0
            vol = np.pi * (q0[-1] + q0[1] / 2.)**2
            if np.isclose(q0[0], 0):
                weight0 = np.pi * (q0[1] / 2.)**2 / vol
                c = (1 - weight0) / np.sum(q0)
                weights = c * q0
                weights[0] = weight0
            else:
                c = 1 / np.sum(q0)
                weights = c * q0

            dWgw_qw[0] = (
                np.repeat(weights[:, np.newaxis], len(w_grid), axis=1) *
                dW_qw[:len(q0)]).sum(axis=0)

        if not include_q0:  # Omit q=0 contrinution completely.
            dWgw_qw[0] = 0.0

        dWgw_qw = dWgw_qw[isort]  # Put dW back on native grid.
        return dWgw_qw

    def calculate_W_QEH(self, structure, d, layer=0):
        from gpaw.response.qeh import Heterostructure, expand_layers, \
            check_building_blocks

        structure = expand_layers(structure)
        self.w_grid = self.omega_w
        wmax = self.w_grid[-1]
        # qmax = (self.q_grid).max()

        # Single layer
        s = (np.insert(d, 0, d[0]) + np.append(d, d[-1])) / 2.
        d0 = s[layer]
        HS0 = Heterostructure(
            structure=[structure[layer]],
            d=[],
            d0=d0,
            wmax=wmax * Hartree,
            # qmax=qmax / Bohr
        )

        W0_qw = HS0.get_screened_potential()

        # Full heterostructure

        HS = Heterostructure(
            structure=structure,
            d=d,
            wmax=wmax * Hartree,
            # qmax=qmax / Bohr
        )
        W_qw = HS.get_screened_potential(layer=layer)

        # Difference in screened potential:
        dW_qw = W_qw - W0_qw
        self.wqeh = HS.frequencies
        self.qqeh = HS.q_abs

        if self.world.rank == 0:
            pickle.dump((self.qqeh, self.wqeh, dW_qw),
                        open(self.filename + '_dW_qw.pckl', 'wb'))

        return dW_qw

    def find_maximum_frequency(self):
        self.epsmin = 10000.0
        self.epsmax = -10000.0
        for kpt in self.calc.wfs.kpt_u:
            self.epsmin = min(self.epsmin, kpt.eps_n[0])
            self.epsmax = max(self.epsmax, kpt.eps_n[self.nbands - 1])

        print('Minimum eigenvalue: %10.3f eV' % (self.epsmin * Hartree),
              file=self.fd)
        print('Maximum eigenvalue: %10.3f eV' % (self.epsmax * Hartree),
              file=self.fd)

        return self.epsmax - self.epsmin
Exemple #19
0
class UTGaussianWavefunctionSetup(UTDomainParallelSetup):
    __doc__ = UTDomainParallelSetup.__doc__ + """
    The pseudo wavefunctions are moving gaussians centered around each atom."""

    allocated = False
    dtype = None

    # Default arguments for scaled Gaussian wave
    _sigma0 = 2.0 #0.75
    _k0_c = 2*np.pi*np.array([1/5., 1/3., 0.])

    def setUp(self):
        UTDomainParallelSetup.setUp(self)

        for virtvar in ['dtype']:
            assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar

        # Create randomized atoms
        self.atoms = create_random_atoms(self.gd, 5) # also tested: 10xNH3/BDA

        # XXX DEBUG START
        if False:
            from ase import view
            view(self.atoms*(1+2*self.gd.pbc_c))
        # XXX DEBUG END

        # Do we agree on the atomic positions?
        pos_ac = self.atoms.get_positions()
        pos_rac = np.empty((world.size,)+pos_ac.shape, pos_ac.dtype)
        world.all_gather(pos_ac, pos_rac)
        if (pos_rac-pos_rac[world.rank,...][np.newaxis,...]).any():
            raise RuntimeError('Discrepancy in atomic positions detected.')

        # Create setups for atoms
        self.Z_a = self.atoms.get_atomic_numbers()
        self.setups = Setups(self.Z_a, p.setups, p.basis,
                             p.lmax, xc)

        # K-point descriptor
        bzk_kc = np.array([[0, 0, 0]], dtype=float)
        self.kd = KPointDescriptor(bzk_kc, 1)
        self.kd.set_symmetry(self.atoms, self.setups)
        self.kd.set_communicator(self.kpt_comm)

        # Create gamma-point dummy wavefunctions
        self.wfs = FDWFS(self.gd, self.bd, self.kd, self.setups,
                         self.block_comm, self.dtype)

        spos_ac = self.atoms.get_scaled_positions() % 1.0
        self.wfs.set_positions(spos_ac)
        self.pt = self.wfs.pt # XXX shortcut

        ## Also create pseudo partial waveves
        #from gpaw.lfc import LFC
        #self.phit = LFC(self.gd, [setup.phit_j for setup in self.setups], \
        #                self.kpt_comm, dtype=self.dtype)
        #self.phit.set_positions(spos_ac)

        self.r_cG = None
        self.buf_G = None
        self.psit_nG = None

        self.allocate()

    def tearDown(self):
        UTDomainParallelSetup.tearDown(self)
        del self.r_cG, self.buf_G, self.psit_nG
        del self.pt, self.setups, self.atoms
        self.allocated = False

    def allocate(self):
        self.r_cG = self.gd.get_grid_point_coordinates()
        cell_cv = self.atoms.get_cell() / Bohr
        assert np.abs(cell_cv-self.gd.cell_cv).max() < 1e-9
        center_c = 0.5*cell_cv.diagonal()

        self.buf_G = self.gd.empty(dtype=self.dtype)
        self.psit_nG = self.gd.empty(self.bd.mynbands, dtype=self.dtype)

        for myn,psit_G in enumerate(self.psit_nG):
            n = self.bd.global_index(myn)
            psit_G[:] = self.get_scaled_gaussian_wave(center_c, scale=10+2j*n)

            k_c = 2*np.pi*np.array([1/2., -1/7., 0.])
            for pos_c in self.atoms.get_positions() / Bohr:
                sigma = self._sigma0/(1+np.sum(pos_c**2))**0.5
                psit_G += self.get_scaled_gaussian_wave(pos_c, sigma, k_c, n+5j)

        self.allocated = True

    def get_scaled_gaussian_wave(self, pos_c, sigma=None, k_c=None, scale=None):
        if sigma is None:
            sigma = self._sigma0

        if k_c is None:
            k_c = self._k0_c

        if scale is None:
            A = None
        else:
            # 4*pi*int(exp(-r^2/(2*w^2))^2*r^2, r=0...infinity)= w^3*pi^(3/2)
            # = scale/A^2 -> A = scale*(sqrt(Pi)*w)^(-3/2) hence int -> scale^2
            A = scale/(sigma*(np.pi)**0.5)**1.5

        return gaussian_wave(self.r_cG, pos_c, sigma, k_c, A, self.dtype, self.buf_G)

    def check_and_plot(self, P_ani, P0_ani, digits, keywords=''):
        # Collapse into viewable matrices
        P_In = self.wfs.collect_projections(P_ani)
        P0_In = self.wfs.collect_projections(P0_ani)

        # Construct fingerprint of input matrices for comparison
        fingerprint = np.array([md5_array(P_In, numeric=True),
                                md5_array(P0_In, numeric=True)])

        # Compare fingerprints across all processors
        fingerprints = np.empty((world.size, 2), np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        # If assertion fails, catch temporarily while plotting, then re-raise
        try:
            self.assertAlmostEqual(np.abs(P_In-P0_In).max(), 0, digits)
        except AssertionError:
            if world.rank == 0 and mpl is not None:
                from matplotlib.figure import Figure
                fig = Figure()
                ax = fig.add_axes([0.0, 0.1, 1.0, 0.83])
                ax.set_title(self.__class__.__name__)
                im = ax.imshow(np.abs(P_In-P0_In), interpolation='nearest')
                fig.colorbar(im)
                fig.text(0.5, 0.05, 'Keywords: ' + keywords, \
                    horizontalalignment='center', verticalalignment='top')

                from matplotlib.backends.backend_agg import FigureCanvasAgg
                img = 'ut_invops_%s_%s.png' % (self.__class__.__name__, \
                    '_'.join(keywords.split(',')))
                FigureCanvasAgg(fig).print_figure(img.lower(), dpi=90)
            raise

    # =================================

    def test_projection_linearity(self):
        kpt = self.wfs.kpt_u[0]
        Q_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(self.psit_nG, Q_ani, q=kpt.q)

        for Q_ni in Q_ani.values():
            self.assertTrue(Q_ni.dtype == self.dtype)

        P0_ani = dict([(a,Q_ni.copy()) for a,Q_ni in Q_ani.items()])
        self.pt.add(self.psit_nG, Q_ani, q=kpt.q)
        self.pt.integrate(self.psit_nG, P0_ani, q=kpt.q)

        #rank_a = self.gd.get_ranks_from_positions(spos_ac)
        #my_atom_indices = np.argwhere(self.gd.comm.rank == rank_a).ravel()

        #                                                ~ a   ~ a'
        #TODO XXX should fix PairOverlap-ish stuff for < p  | phi  > overlaps
        #                                                 i      i'

        #spos_ac = self.pt.spos_ac # NewLFC doesn't have this
        spos_ac = self.atoms.get_scaled_positions() % 1.0
        gpo = GridPairOverlap(self.gd, self.setups)
        B_aa = gpo.calculate_overlaps(spos_ac, self.pt)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        P_ani = dict([(a,Q_ni.copy()) for a,Q_ni in Q_ani.items()])
        for a1 in range(len(self.atoms)):
            if a1 in P_ani.keys():
                P_ni = P_ani[a1]
            else:
                # Atom a1 is not in domain so allocate a temporary buffer
                P_ni = np.zeros((self.bd.mynbands,self.setups[a1].ni,),
                                 dtype=self.dtype)
            for a2, Q_ni in Q_ani.items():
                # B_aa are the projector overlaps across atomic pairs
                B_ii = gpo.extract_atomic_pair_matrix(B_aa, a1, a2)
                P_ni += np.dot(Q_ni, B_ii.T) #sum over a2 and last i in B_ii
            self.gd.comm.sum(P_ni)

        self.check_and_plot(P_ani, P0_ani, 8, 'projection,linearity')

    def test_extrapolate_overlap(self):
        kpt = self.wfs.kpt_u[0]
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        P_ani = ppo.apply(self.psit_nG, work_nG, self.wfs, kpt, \
            calculate_P_ani=True, extrapolate_P_ani=True)

        P0_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(work_nG, P0_ani, kpt.q)
        del work_nG

        self.check_and_plot(P_ani, P0_ani, 11, 'extrapolate,overlap')

    def test_extrapolate_inverse(self):
        kpt = self.wfs.kpt_u[0]
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        P_ani = ppo.apply_inverse(self.psit_nG, work_nG, self.wfs, kpt, \
            calculate_P_ani=True, extrapolate_P_ani=True)

        P0_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(work_nG, P0_ani, kpt.q)
        del work_nG

        self.check_and_plot(P_ani, P0_ani, 11, 'extrapolate,inverse')

    def test_overlap_inverse_after(self):
        kpt = self.wfs.kpt_u[0]
        kpt.P_ani = self.pt.dict(self.bd.mynbands)
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        self.pt.integrate(self.psit_nG, kpt.P_ani, kpt.q)
        P0_ani = dict([(a,P_ni.copy()) for a,P_ni in kpt.P_ani.items()])
        ppo.apply(self.psit_nG, work_nG, self.wfs, kpt, calculate_P_ani=False)

        res_nG = np.empty_like(self.psit_nG)
        ppo.apply_inverse(work_nG, res_nG, self.wfs, kpt, calculate_P_ani=True)
        del work_nG

        P_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(res_nG, P_ani, kpt.q)

        abserr = np.empty(1, dtype=float)
        for n in range(self.nbands):
            band_rank, myn = self.bd.who_has(n)
            if band_rank == self.bd.comm.rank:
                abserr[:] = np.abs(self.psit_nG[myn] - res_nG[myn]).max()
                self.gd.comm.max(abserr)
            self.bd.comm.broadcast(abserr, band_rank)
            self.assertAlmostEqual(abserr.item(), 0, 10)

        self.check_and_plot(P_ani, P0_ani, 10, 'overlap,inverse,after')

    def test_overlap_inverse_before(self):
        kpt = self.wfs.kpt_u[0]
        kpt.P_ani = self.pt.dict(self.bd.mynbands)
        ppo = ProjectorPairOverlap(self.wfs, self.atoms)

        # Compare fingerprints across all processors
        fingerprint = np.array([md5_array(ppo.B_aa, numeric=True)])
        fingerprints = np.empty(world.size, np.int64)
        world.all_gather(fingerprint, fingerprints)
        if fingerprints.ptp(0).any():
            raise RuntimeError('Distributed matrices are not identical!')

        work_nG = np.empty_like(self.psit_nG)
        self.pt.integrate(self.psit_nG, kpt.P_ani, kpt.q)
        P0_ani = dict([(a,P_ni.copy()) for a,P_ni in kpt.P_ani.items()])
        ppo.apply_inverse(self.psit_nG, work_nG, self.wfs, kpt, calculate_P_ani=False)

        res_nG = np.empty_like(self.psit_nG)
        ppo.apply(work_nG, res_nG, self.wfs, kpt, calculate_P_ani=True)
        del work_nG

        P_ani = self.pt.dict(self.bd.mynbands)
        self.pt.integrate(res_nG, P_ani, kpt.q)

        abserr = np.empty(1, dtype=float)
        for n in range(self.nbands):
            band_rank, myn = self.bd.who_has(n)
            if band_rank == self.bd.comm.rank:
                abserr[:] = np.abs(self.psit_nG[myn] - res_nG[myn]).max()
                self.gd.comm.max(abserr)
            self.bd.comm.broadcast(abserr, band_rank)
            self.assertAlmostEqual(abserr.item(), 0, 10)

        self.check_and_plot(P_ani, P0_ani, 10, 'overlap,inverse,before')
Exemple #20
0
class UTKPointParallelSetup(TestCase):
    """
    Setup a simple kpoint parallel calculation."""

    # Number of bands
    nbands = 1

    # Spin-polarized
    nspins = 1

    # Mean spacing and number of grid points per axis (G x G x G)
    h = 0.25 / Bohr
    G = 48

    ## Symmetry-reduction of k-points TODO
    #symmetry = p.usesymm #XXX 'None' is an allowed value!!!

    # Whether spin/k-points are equally distributed (determines nibzkpts)
    equipartition = None
    nibzkpts = None

    gamma = False # can't be gamma point when nibzkpts > 1 ...
    dtype = complex #XXX virtual so far..

    # =================================

    def setUp(self):
        for virtvar in ['equipartition']:
            assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar

        kpts = {'even' : (12,1,2), \
                'prime': (23,1,1)}[self.equipartition]

        #primes = [i for i in xrange(50,1,-1) if ~np.any(i%np.arange(2,i)==0)]
        bzk_kc = kpts2ndarray(kpts)
        assert p.usesymm == None
        self.nibzkpts = len(bzk_kc)

        #parsize_domain, parsize_bands = create_parsize_minbands(self.nbands, world.size)
        parsize_domain, parsize_bands = 1, 1 #XXX
        assert self.nbands % np.prod(parsize_bands) == 0
        domain_comm, kpt_comm, band_comm = distribute_cpus(parsize_domain,
            parsize_bands, self.nspins, self.nibzkpts)

        # Set up band descriptor:
        self.bd = BandDescriptor(self.nbands, band_comm, p.parallel['stridebands'])

        # Set up grid descriptor:
        res, ngpts = shapeopt(300, self.G**3, 3, 0.2)
        cell_c = self.h * np.array(ngpts)
        pbc_c = (True, False, True)
        self.gd = GridDescriptor(ngpts, cell_c, pbc_c, domain_comm, parsize_domain)

        # Create randomized gas-like atomic configuration
        self.atoms = create_random_atoms(self.gd)

        # Create setups
        Z_a = self.atoms.get_atomic_numbers()
        self.setups = Setups(Z_a, p.setups, p.basis, p.lmax, xc)
        self.natoms = len(self.setups)

        # Set up kpoint descriptor:
        self.kd = KPointDescriptor(bzk_kc, self.nspins)
        self.kd.set_symmetry(self.atoms, self.setups, usesymm=p.usesymm)
        self.kd.set_communicator(kpt_comm)

    def tearDown(self):
        del self.bd, self.gd, self.kd
        del self.setups, self.atoms

    def get_parsizes(self): #XXX NO LONGER IN UT_HSOPS?!?
        # Careful, overwriting imported GPAW params may cause amnesia in Python.
        from gpaw import parsize_domain, parsize_bands

        # Choose the largest possible parallelization over kpoint/spins
        test_parsize_ks_pairs = gcd(self.nspins*self.nibzkpts, world.size)
        remsize = world.size//test_parsize_ks_pairs

        # If parsize_bands is not set, choose the largest possible
        test_parsize_bands = parsize_bands or gcd(self.nbands, remsize)

        # If parsize_bands is not set, choose as few domains as possible
        test_parsize_domain = parsize_domain or (remsize//test_parsize_bands)

        return test_parsize_domain, test_parsize_bands

    # =================================

    def verify_comm_sizes(self): #TODO needs work
        if world.size == 1:
            return
        comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \
                                                   self.gd.comm, self.kd.comm]])
        self._parinfo =  '%d world, %d band, %d domain, %d kpt' % comm_sizes
        #self.assertEqual((self.nspins*self.nibzkpts) % self.kd.comm.size, 0) #XXX

    def verify_slice_consistency(self):
        for kpt_rank in range(self.kd.comm.size):
            uslice = self.kd.get_slice(kpt_rank)
            myus = np.arange(*uslice.indices(self.kd.nks))
            for myu,u in enumerate(myus):
                self.assertEqual(self.kd.who_has(u), (kpt_rank, myu))

    def verify_combination_consistency(self):
        for u in range(self.kd.nks):
            s, k = self.kd.what_is(u)
            self.assertEqual(self.kd.where_is(s, k), u)

        for s in range(self.kd.nspins):
            for k in range(self.kd.nibzkpts):
                u = self.kd.where_is(s, k)
                self.assertEqual(self.kd.what_is(u), (s,k,))

    def verify_indexing_consistency(self):
        for u in range(self.kd.nks):
            kpt_rank, myu = self.kd.who_has(u)
            self.assertEqual(self.kd.global_index(myu, kpt_rank), u)

        for kpt_rank in range(self.kd.comm.size):
            for myu in range(self.kd.get_count(kpt_rank)):
                u = self.kd.global_index(myu, kpt_rank)
                self.assertEqual(self.kd.who_has(u), (kpt_rank, myu))

    def verify_ranking_consistency(self):
        ranks = self.kd.get_ranks()

        for kpt_rank in range(self.kd.comm.size):
            my_indices = self.kd.get_indices(kpt_rank)
            matches = np.argwhere(ranks == kpt_rank).ravel()
            self.assertTrue((matches == my_indices).all())
            for myu in range(self.kd.get_count(kpt_rank)):
                u = self.kd.global_index(myu, kpt_rank)
                self.assertEqual(my_indices[myu], u)
Exemple #21
0
    def initialize(self, atoms=None):
        """Inexpensive initialization."""

        if atoms is None:
            atoms = self.atoms
        else:
            # Save the state of the atoms:
            self.atoms = atoms.copy()

        par = self.input_parameters

        world = par.communicator
        if world is None:
            world = mpi.world
        elif hasattr(world, 'new_communicator'):
            # Check for whether object has correct type already
            #
            # Using isinstance() is complicated because of all the
            # combinations, serial/parallel/debug...
            pass
        else:
            # world should be a list of ranks:
            world = mpi.world.new_communicator(np.asarray(world))
        self.wfs.world = world

        self.set_text(par.txt, par.verbose)

        natoms = len(atoms)

        cell_cv = atoms.get_cell() / Bohr
        pbc_c = atoms.get_pbc()
        Z_a = atoms.get_atomic_numbers()
        magmom_av = atoms.get_initial_magnetic_moments()

        # Generate new xc functional only when it is reset by set
        if self.hamiltonian is None or self.hamiltonian.xc is None:
            if isinstance(par.xc, str):
                xc = XC(par.xc)
            else:
                xc = par.xc
        else:
            xc = self.hamiltonian.xc

        mode = par.mode

        if xc.orbital_dependent and mode == 'lcao':
            raise NotImplementedError('LCAO mode does not support '
                                      'orbital-dependent XC functionals.')

        if mode == 'pw':
            mode = PW()

        if mode == 'fd' and par.usefractrans:
            raise NotImplementedError('FD mode does not support '
                                      'fractional translations.')
        
        if mode == 'lcao' and par.usefractrans:
            raise Warning('Fractional translations have not been tested '
                          'with LCAO mode. Use with care!')

        if par.realspace is None:
            realspace = not isinstance(mode, PW)
        else:
            realspace = par.realspace
            if isinstance(mode, PW):
                assert not realspace

        if par.gpts is not None:
            N_c = np.array(par.gpts)
        else:
            h = par.h
            if h is not None:
                h /= Bohr
            N_c = get_number_of_grid_points(cell_cv, h, mode, realspace)

        if par.filter is None and not isinstance(mode, PW):
            gamma = 1.6
            hmax = ((np.linalg.inv(cell_cv)**2).sum(0)**-0.5 / N_c).max()
            
            def filter(rgd, rcut, f_r, l=0):
                gcut = np.pi / hmax - 2 / rcut / gamma
                f_r[:] = rgd.filter(f_r, rcut * gamma, gcut, l)
        else:
            filter = par.filter

        setups = Setups(Z_a, par.setups, par.basis, par.lmax, xc,
                        filter, world)

        if magmom_av.ndim == 1:
            collinear = True
            magmom_av, magmom_a = np.zeros((natoms, 3)), magmom_av
            magmom_av[:, 2] = magmom_a
        else:
            collinear = False
            
        magnetic = magmom_av.any()

        spinpol = par.spinpol
        if par.hund:
            if natoms != 1:
                raise ValueError('hund=True arg only valid for single atoms!')
            spinpol = True
            magmom_av[0] = (0, 0, setups[0].get_hunds_rule_moment(par.charge))
            
        if spinpol is None:
            spinpol = magnetic
        elif magnetic and not spinpol:
            raise ValueError('Non-zero initial magnetic moment for a ' +
                             'spin-paired calculation!')

        if collinear:
            nspins = 1 + int(spinpol)
            ncomp = 1
        else:
            nspins = 1
            ncomp = 2

        # K-point descriptor
        bzkpts_kc = kpts2ndarray(par.kpts, self.atoms)
        kd = KPointDescriptor(bzkpts_kc, nspins, collinear, par.usefractrans)

        width = par.width
        if width is None:
            if pbc_c.any():
                width = 0.1  # eV
            else:
                width = 0.0
        else:
            assert par.occupations is None
      
        if hasattr(self, 'time') or par.dtype == complex:
            dtype = complex
        else:
            if kd.gamma:
                dtype = float
            else:
                dtype = complex

        ## rbw: If usefractrans=True, kd.set_symmetry might overwrite N_c.
        ## This is necessary, because N_c must be dividable by 1/(fractional translation),
        ## f.e. fractional translations of a grid point must land on a grid point.
        N_c = kd.set_symmetry(atoms, setups, magmom_av, par.usesymm, N_c, world)

        nao = setups.nao
        nvalence = setups.nvalence - par.charge
        M_v = magmom_av.sum(0)
        M = np.dot(M_v, M_v)**0.5
        
        nbands = par.nbands
        if nbands is None:
            nbands = 0
            for setup in setups:
                nbands_from_atom = setup.get_default_nbands()
                
                # Any obscure setup errors?
                if nbands_from_atom < -(-setup.Nv // 2):
                    raise ValueError('Bad setup: This setup requests %d'
                                     ' bands but has %d electrons.'
                                     % (nbands_from_atom, setup.Nv))
                nbands += nbands_from_atom
            nbands = min(nao, nbands)
        elif nbands > nao and mode == 'lcao':
            raise ValueError('Too many bands for LCAO calculation: '
                             '%d bands and only %d atomic orbitals!' %
                             (nbands, nao))

        if nvalence < 0:
            raise ValueError(
                'Charge %f is not possible - not enough valence electrons' %
                par.charge)

        if nbands <= 0:
            nbands = int(nvalence + M + 0.5) // 2 + (-nbands)

        if nvalence > 2 * nbands:
            raise ValueError('Too few bands!  Electrons: %f, bands: %d'
                             % (nvalence, nbands))

        nbands *= ncomp

        if par.width is not None:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width).')
        if par.fixmom:
            self.text('**NOTE**: please start using '
                      'occupations=FermiDirac(width, fixmagmom=True).')

        if self.occupations is None:
            if par.occupations is None:
                # Create object for occupation numbers:
                self.occupations = occupations.FermiDirac(width, par.fixmom)
            else:
                self.occupations = par.occupations

        self.occupations.magmom = M_v[2]

        cc = par.convergence

        if mode == 'lcao':
            niter_fixdensity = 0
        else:
            niter_fixdensity = None

        if self.scf is None:
            self.scf = SCFLoop(
                cc['eigenstates'] / Hartree**2 * nvalence,
                cc['energy'] / Hartree * max(nvalence, 1),
                cc['density'] * nvalence,
                par.maxiter, par.fixdensity,
                niter_fixdensity)

        parsize_kpt = par.parallel['kpt']
        parsize_domain = par.parallel['domain']
        parsize_bands = par.parallel['band']

        if not realspace:
            pbc_c = np.ones(3, bool)

        if not self.wfs:
            if parsize_domain == 'domain only':  # XXX this was silly!
                parsize_domain = world.size

            parallelization = mpi.Parallelization(world,
                                                  nspins * kd.nibzkpts)
            ndomains = None
            if parsize_domain is not None:
                ndomains = np.prod(parsize_domain)
            if isinstance(mode, PW):
                if ndomains > 1:
                    raise ValueError('Planewave mode does not support '
                                     'domain decomposition.')
                ndomains = 1
            parallelization.set(kpt=parsize_kpt,
                                domain=ndomains,
                                band=parsize_bands)
            domain_comm, kpt_comm, band_comm = \
                parallelization.build_communicators()

            #domain_comm, kpt_comm, band_comm = mpi.distribute_cpus(
            #    parsize_domain, parsize_bands,
            #    nspins, kd.nibzkpts, world, par.idiotproof, mode)

            kd.set_communicator(kpt_comm)

            parstride_bands = par.parallel['stridebands']

            # Unfortunately we need to remember that we adjusted the
            # number of bands so we can print a warning if it differs
            # from the number specified by the user.  (The number can
            # be inferred from the input parameters, but it's tricky
            # because we allow negative numbers)
            self.nbands_parallelization_adjustment = -nbands % band_comm.size
            nbands += self.nbands_parallelization_adjustment

            # I would like to give the following error message, but apparently
            # there are cases, e.g. gpaw/test/gw_ppa.py, which involve
            # nbands > nao and are supposed to work that way.
            #if nbands > nao:
            #    raise ValueError('Number of bands %d adjusted for band '
            #                     'parallelization %d exceeds number of atomic '
            #                     'orbitals %d.  This problem can be fixed '
            #                     'by reducing the number of bands a bit.'
            #                     % (nbands, band_comm.size, nao))
            bd = BandDescriptor(nbands, band_comm, parstride_bands)

            if (self.density is not None and
                self.density.gd.comm.size != domain_comm.size):
                # Domain decomposition has changed, so we need to
                # reinitialize density and hamiltonian:
                if par.fixdensity:
                    raise RuntimeError('Density reinitialization conflict ' +
                        'with "fixdensity" - specify domain decomposition.')
                self.density = None
                self.hamiltonian = None

            # Construct grid descriptor for coarse grids for wave functions:
            gd = self.grid_descriptor_class(N_c, cell_cv, pbc_c,
                                            domain_comm, parsize_domain)

            # do k-point analysis here? XXX
            args = (gd, nvalence, setups, bd, dtype, world, kd, self.timer)

            if par.parallel['sl_auto']:
                # Choose scalapack parallelization automatically
                
                for key, val in par.parallel.items():
                    if (key.startswith('sl_') and key != 'sl_auto'
                        and val is not None):
                        raise ValueError("Cannot use 'sl_auto' together "
                                         "with '%s'" % key)
                max_scalapack_cpus = bd.comm.size * gd.comm.size
                nprow = max_scalapack_cpus
                npcol = 1
                
                # Get a sort of reasonable number of columns/rows
                while npcol < nprow and nprow % 2 == 0:
                    npcol *= 2
                    nprow //= 2
                assert npcol * nprow == max_scalapack_cpus

                # ScaLAPACK creates trouble if there aren't at least a few
                # whole blocks; choose block size so there will always be
                # several blocks.  This will crash for small test systems,
                # but so will ScaLAPACK in any case
                blocksize = min(-(-nbands // 4), 64)
                sl_default = (nprow, npcol, blocksize)
            else:
                sl_default = par.parallel['sl_default']

            if mode == 'lcao':
                # Layouts used for general diagonalizer
                sl_lcao = par.parallel['sl_lcao']
                if sl_lcao is None:
                    sl_lcao = sl_default
                lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao',
                                               gd, bd, dtype,
                                               nao=nao, timer=self.timer)

                if collinear:
                    self.wfs = LCAOWaveFunctions(lcaoksl, *args)
                else:
                    from gpaw.xc.noncollinear import \
                         NonCollinearLCAOWaveFunctions
                    self.wfs = NonCollinearLCAOWaveFunctions(lcaoksl, *args)

            elif mode == 'fd' or isinstance(mode, PW):
                # buffer_size keyword only relevant for fdpw
                buffer_size = par.parallel['buffer_size']
                # Layouts used for diagonalizer
                sl_diagonalize = par.parallel['sl_diagonalize']
                if sl_diagonalize is None:
                    sl_diagonalize = sl_default
                diagksl = get_KohnSham_layouts(sl_diagonalize, 'fd',
                                               gd, bd, dtype,
                                               buffer_size=buffer_size,
                                               timer=self.timer)

                # Layouts used for orthonormalizer
                sl_inverse_cholesky = par.parallel['sl_inverse_cholesky']
                if sl_inverse_cholesky is None:
                    sl_inverse_cholesky = sl_default
                if sl_inverse_cholesky != sl_diagonalize:
                    message = 'sl_inverse_cholesky != sl_diagonalize ' \
                        'is not implemented.'
                    raise NotImplementedError(message)
                orthoksl = get_KohnSham_layouts(sl_inverse_cholesky, 'fd',
                                                gd, bd, dtype,
                                                buffer_size=buffer_size,
                                                timer=self.timer)

                # Use (at most) all available LCAO for initialization
                lcaonbands = min(nbands, nao)

                try:
                    lcaobd = BandDescriptor(lcaonbands, band_comm,
                                            parstride_bands)
                except RuntimeError:
                    initksl = None
                else:
                    # Layouts used for general diagonalizer
                    # (LCAO initialization)
                    sl_lcao = par.parallel['sl_lcao']
                    if sl_lcao is None:
                        sl_lcao = sl_default
                    initksl = get_KohnSham_layouts(sl_lcao, 'lcao',
                                                   gd, lcaobd, dtype,
                                                   nao=nao,
                                                   timer=self.timer)

                if hasattr(self, 'time'):
                    assert mode == 'fd'
                    from gpaw.tddft import TimeDependentWaveFunctions
                    self.wfs = TimeDependentWaveFunctions(par.stencils[0],
                        diagksl, orthoksl, initksl, gd, nvalence, setups,
                        bd, world, kd, self.timer)
                elif mode == 'fd':
                    self.wfs = FDWaveFunctions(par.stencils[0], diagksl,
                                               orthoksl, initksl, *args)
                else:
                    # Planewave basis:
                    self.wfs = mode(diagksl, orthoksl, initksl, *args)
            else:
                self.wfs = mode(self, *args)
        else:
            self.wfs.set_setups(setups)

        if not self.wfs.eigensolver:
            # Number of bands to converge:
            nbands_converge = cc['bands']
            if nbands_converge == 'all':
                nbands_converge = nbands
            elif nbands_converge != 'occupied':
                assert isinstance(nbands_converge, int)
                if nbands_converge < 0:
                    nbands_converge += nbands
            eigensolver = get_eigensolver(par.eigensolver, mode,
                                          par.convergence)
            eigensolver.nbands_converge = nbands_converge
            # XXX Eigensolver class doesn't define an nbands_converge property

            if isinstance(xc, SIC):
                eigensolver.blocksize = 1
            self.wfs.set_eigensolver(eigensolver)

        if self.density is None:
            gd = self.wfs.gd
            if par.stencils[1] != 9:
                # Construct grid descriptor for fine grids for densities
                # and potentials:
                finegd = gd.refine()
            else:
                # Special case (use only coarse grid):
                finegd = gd

            if realspace:
                self.density = RealSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear, par.stencils[1])
            else:
                self.density = ReciprocalSpaceDensity(
                    gd, finegd, nspins, par.charge + setups.core_charge,
                    collinear)

        self.density.initialize(setups, self.timer, magmom_av, par.hund)
        self.density.set_mixer(par.mixer)

        if self.hamiltonian is None:
            gd, finegd = self.density.gd, self.density.finegd
            if realspace:
                self.hamiltonian = RealSpaceHamiltonian(
                    gd, finegd, nspins, setups, self.timer, xc, par.external,
                    collinear, par.poissonsolver, par.stencils[1], world)
            else:
                self.hamiltonian = ReciprocalSpaceHamiltonian(
                    gd, finegd,
                    self.density.pd2, self.density.pd3,
                    nspins, setups, self.timer, xc, par.external,
                    collinear, world)
            
        xc.initialize(self.density, self.hamiltonian, self.wfs,
                      self.occupations)

        self.text()
        self.print_memory_estimate(self.txt, maxdepth=memory_estimate_depth)
        self.txt.flush()

        self.timer.print_info(self)
        
        if dry_run:
            self.dry_run()
        
        self.initialized = True
Exemple #22
0
class UTDomainParallelSetup(TestCase):
    """
    Setup a simple domain parallel calculation."""

    # Number of bands
    nbands = 12

    # Spin-polarized
    nspins = 1

    # Mean spacing and number of grid points per axis (G x G x G)
    h = 0.25 / Bohr
    G = 48

    # Type of boundary conditions employed (determines nibzkpts and dtype)
    boundaries = None
    nibzkpts = None
    dtype = None

    timer = nulltimer

    def setUp(self):
        for virtvar in ['boundaries']:
            assert getattr(self,
                           virtvar) is not None, 'Virtual "%s"!' % virtvar

        # Basic unit cell information:
        res, N_c = shapeopt(100, self.G**3, 3, 0.2)
        #N_c = 4*np.round(np.array(N_c)/4) # makes domain decomposition easier
        cell_cv = self.h * np.diag(N_c)
        pbc_c = {'zero'    : (False,False,False), \
                 'periodic': (True,True,True), \
                 'mixed'   : (True, False, True)}[self.boundaries]

        # Create randomized gas-like atomic configuration on interim grid
        tmpgd = GridDescriptor(N_c, cell_cv, pbc_c)
        self.atoms = create_random_atoms(tmpgd)

        # Create setups
        Z_a = self.atoms.get_atomic_numbers()
        assert 1 == self.nspins
        self.setups = Setups(Z_a, p.setups, p.basis, p.lmax, xc)
        self.natoms = len(self.setups)

        # Decide how many kpoints to sample from the 1st Brillouin Zone
        kpts_c = np.ceil(
            (10 / Bohr) / np.sum(cell_cv**2, axis=1)**0.5).astype(int)
        kpts_c = tuple(kpts_c * pbc_c + 1 - pbc_c)
        self.bzk_kc = kpts2ndarray(kpts_c)

        # Set up k-point descriptor
        self.kd = KPointDescriptor(self.bzk_kc, self.nspins)
        self.kd.set_symmetry(self.atoms, self.setups, p.usesymm)

        # Set the dtype
        if self.kd.gamma:
            self.dtype = float
        else:
            self.dtype = complex

        # Create communicators
        parsize, parsize_bands = self.get_parsizes()
        assert self.nbands % np.prod(parsize_bands) == 0
        domain_comm, kpt_comm, band_comm = distribute_cpus(
            parsize, parsize_bands, self.nspins, self.kd.nibzkpts)

        self.kd.set_communicator(kpt_comm)

        # Set up band descriptor:
        self.bd = BandDescriptor(self.nbands, band_comm)

        # Set up grid descriptor:
        self.gd = GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize)

        # Set up kpoint/spin descriptor (to be removed):
        self.kd_old = KPointDescriptorOld(self.nspins, self.kd.nibzkpts,
                                          kpt_comm, self.kd.gamma, self.dtype)

    def tearDown(self):
        del self.atoms, self.bd, self.gd, self.kd, self.kd_old

    def get_parsizes(self):
        # Careful, overwriting imported GPAW params may cause amnesia in Python.
        from gpaw import parsize, parsize_bands

        # D: number of domains
        # B: number of band groups
        if parsize is None:
            D = min(world.size, 2)
        else:
            D = parsize
        assert world.size % D == 0
        if parsize_bands is None:
            B = world.size // D
        else:
            B = parsize_bands
        return D, B

    # =================================

    def verify_comm_sizes(self):
        if world.size == 1:
            return
        comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \
                                                   self.gd.comm, self.kd_old.comm]])
        self._parinfo = '%d world, %d band, %d domain, %d kpt' % comm_sizes
        self.assertEqual(self.nbands % self.bd.comm.size, 0)
        self.assertEqual(
            (self.nspins * self.kd.nibzkpts) % self.kd_old.comm.size, 0)
Exemple #23
0
class UTDomainParallelSetup(TestCase):
    """
    Setup a simple domain parallel calculation."""

    # Number of bands
    nbands = 12

    # Spin-polarized
    nspins = 1

    # Mean spacing and number of grid points per axis (G x G x G)
    h = 0.25 / Bohr
    G = 48

    # Type of boundary conditions employed (determines nibzkpts and dtype)
    boundaries = None
    nibzkpts = None
    dtype = None

    timer = nulltimer

    def setUp(self):
        for virtvar in ['boundaries']:
            assert getattr(self,virtvar) is not None, 'Virtual "%s"!' % virtvar

        # Basic unit cell information:
        res, N_c = shapeopt(100, self.G**3, 3, 0.2)
        #N_c = 4*np.round(np.array(N_c)/4) # makes domain decomposition easier
        cell_cv = self.h * np.diag(N_c)
        pbc_c = {'zero'    : (False,False,False), \
                 'periodic': (True,True,True), \
                 'mixed'   : (True, False, True)}[self.boundaries]

        # Create randomized gas-like atomic configuration on interim grid
        tmpgd = GridDescriptor(N_c, cell_cv, pbc_c)
        self.atoms = create_random_atoms(tmpgd)

        # Create setups
        Z_a = self.atoms.get_atomic_numbers()
        assert 1 == self.nspins
        self.setups = Setups(Z_a, p.setups, p.basis, p.lmax, xc)
        self.natoms = len(self.setups)

        # Decide how many kpoints to sample from the 1st Brillouin Zone
        kpts_c = np.ceil((10/Bohr)/np.sum(cell_cv**2,axis=1)**0.5).astype(int)
        kpts_c = tuple(kpts_c * pbc_c + 1 - pbc_c)
        self.bzk_kc = kpts2ndarray(kpts_c)

        # Set up k-point descriptor
        self.kd = KPointDescriptor(self.bzk_kc, self.nspins)
        self.kd.set_symmetry(self.atoms, self.setups, p.usesymm)

        # Set the dtype
        if self.kd.gamma:
            self.dtype = float
        else:
            self.dtype = complex
            
        # Create communicators
        parsize, parsize_bands = self.get_parsizes()
        assert self.nbands % np.prod(parsize_bands) == 0
        domain_comm, kpt_comm, band_comm = distribute_cpus(parsize,
            parsize_bands, self.nspins, self.kd.nibzkpts)

        self.kd.set_communicator(kpt_comm)
        
        # Set up band descriptor:
        self.bd = BandDescriptor(self.nbands, band_comm)

        # Set up grid descriptor:
        self.gd = GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, parsize)

        # Set up kpoint/spin descriptor (to be removed):
        self.kd_old = KPointDescriptorOld(self.nspins, self.kd.nibzkpts,
                                          kpt_comm, self.kd.gamma, self.dtype)

       
    def tearDown(self):
        del self.atoms, self.bd, self.gd, self.kd, self.kd_old

    def get_parsizes(self):
        # Careful, overwriting imported GPAW params may cause amnesia in Python.
        from gpaw import parsize, parsize_bands

        # D: number of domains
        # B: number of band groups
        if parsize is None:
            D = min(world.size, 2)
        else:
            D = parsize
        assert world.size % D == 0
        if parsize_bands is None:
            B = world.size // D
        else:
            B = parsize_bands
        return D, B

    # =================================

    def verify_comm_sizes(self):
        if world.size == 1:
            return
        comm_sizes = tuple([comm.size for comm in [world, self.bd.comm, \
                                                   self.gd.comm, self.kd_old.comm]])
        self._parinfo =  '%d world, %d band, %d domain, %d kpt' % comm_sizes
        self.assertEqual(self.nbands % self.bd.comm.size, 0)
        self.assertEqual((self.nspins * self.kd.nibzkpts) % self.kd_old.comm.size, 0)
Exemple #24
0
class HybridXC(XCFunctional):
    orbital_dependent = True
    def __init__(self, name, hybrid=None, xc=None, finegrid=False,
                 alpha=None):
        """Mix standard functionals with exact exchange.

        name: str
            Name of hybrid functional.
        hybrid: float
            Fraction of exact exchange.
        xc: str or XCFunctional object
            Standard DFT functional with scaled down exchange.
        finegrid: boolean
            Use fine grid for energy functional evaluations?
        """

        if name == 'EXX':
            assert hybrid is None and xc is None
            hybrid = 1.0
            xc = XC(XCNull())
        elif name == 'PBE0':
            assert hybrid is None and xc is None
            hybrid = 0.25
            xc = XC('HYB_GGA_XC_PBEH')
        elif name == 'B3LYP':
            assert hybrid is None and xc is None
            hybrid = 0.2
            xc = XC('HYB_GGA_XC_B3LYP')
            
        if isinstance(xc, str):
            xc = XC(xc)

        self.hybrid = hybrid
        self.xc = xc
        self.type = xc.type
        self.alpha = alpha
        self.exx = 0.0
        
        XCFunctional.__init__(self, name)

    def get_setup_name(self):
        return 'PBE'

    def calculate_radial(self, rgd, n_sLg, Y_L, v_sg,
                         dndr_sLg=None, rnablaY_Lv=None,
                         tau_sg=None, dedtau_sg=None):
        return self.xc.calculate_radial(rgd, n_sLg, Y_L, v_sg,
                                        dndr_sLg, rnablaY_Lv)
    
    def initialize(self, density, hamiltonian, wfs, occupations):
        self.xc.initialize(density, hamiltonian, wfs, occupations)
        self.nspins = wfs.nspins
        self.setups = wfs.setups
        self.density = density
        self.kpt_u = wfs.kpt_u
        
        self.gd = density.gd
        self.kd = wfs.kd
        self.bd = wfs.bd

        N_c = self.gd.N_c
        N = self.gd.N_c.prod()
        vol = self.gd.dv * N
        
        if self.alpha is None:
            self.alpha = 6 * vol**(2 / 3.0) / pi**2
            
        self.gamma = (vol / (2 * pi)**2 * sqrt(pi / self.alpha) *
                      self.kd.nbzkpts)
        ecut = 0.5 * pi**2 / (self.gd.h_cv**2).sum(1).max()

        if self.kd.N_c is None:
            self.bzk_kc = np.zeros((1, 3))
            dfghdfgh
        else:
            n = self.kd.N_c * 2 - 1
            bzk_kc = np.indices(n).transpose((1, 2, 3, 0))
            bzk_kc.shape = (-1, 3)
            bzk_kc -= self.kd.N_c - 1
            self.bzk_kc = bzk_kc.astype(float) / self.kd.N_c
        
        self.pwd = PWDescriptor(ecut, self.gd, self.bzk_kc)

        n = 0
        for k_c, Gpk2_G in zip(self.bzk_kc[:], self.pwd.G2_qG):
            if (k_c > -0.5).all() and (k_c <= 0.5).all(): #XXX???
                if k_c.any():
                    self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G),
                                         Gpk2_G**-1)
                else:
                    self.gamma -= np.dot(np.exp(-self.alpha * Gpk2_G[1:]),
                                         Gpk2_G[1:]**-1)
                n += 1

        assert n == self.kd.N_c.prod()
        
        self.ghat = LFC(self.gd,
                        [setup.ghat_l for setup in density.setups],
                        dtype=complex
                        )
        self.ghat.set_k_points(self.bzk_kc)
        
        self.fullkd = KPointDescriptor(self.kd.bzk_kc, nspins=1)
        class S:
            id_a = []
            def set_symmetry(self, s): pass
            
        self.fullkd.set_symmetry(Atoms(pbc=True), S(), False)
        self.fullkd.set_communicator(world)
        self.pt = LFC(self.gd, [setup.pt_j for setup in density.setups],
                      dtype=complex)
        self.pt.set_k_points(self.fullkd.ibzk_kc)

        self.interpolator = density.interpolator

    def set_positions(self, spos_ac):
        self.ghat.set_positions(spos_ac)
        self.pt.set_positions(spos_ac)
    
    def calculate(self, gd, n_sg, v_sg=None, e_g=None):
        # Normal XC contribution:
        exc = self.xc.calculate(gd, n_sg, v_sg, e_g)

        # Add EXX contribution:
        return exc + self.exx

    def calculate_exx(self):
        """Non-selfconsistent calculation."""

        kd = self.kd
        K = self.fullkd.nibzkpts
        assert self.nspins == 1
        Q = K // world.size
        assert Q * world.size == K
        parallel = (world.size > self.nspins)
        
        self.exx = 0.0
        self.exx_skn = np.zeros((self.nspins, K, self.bd.nbands))

        kpt_u = []
        for k in range(world.rank * Q, (world.rank + 1) * Q):
            k_c = self.fullkd.ibzk_kc[k]
            for k1, k1_c in enumerate(kd.bzk_kc):
                if abs(k1_c - k_c).max() < 1e-10:
                    break
                
            # Index of symmetry related point in the irreducible BZ
            ik = kd.kibz_k[k1]
            kpt = self.kpt_u[ik]

            # KPoint from ground-state calculation
            phase_cd = np.exp(2j * pi * self.gd.sdisp_cd * k_c[:, np.newaxis])
            kpt2 = KPoint0(kpt.weight, kpt.s, k, None, phase_cd)
            kpt2.psit_nG = np.empty_like(kpt.psit_nG)
            kpt2.f_n = kpt.f_n / kpt.weight / K * 2
            for n, psit_G in enumerate(kpt2.psit_nG):
                psit_G[:] = kd.transform_wave_function(kpt.psit_nG[n], k1)

            kpt2.P_ani = self.pt.dict(len(kpt.psit_nG))
            self.pt.integrate(kpt2.psit_nG, kpt2.P_ani, k)
            kpt_u.append(kpt2)

        for s in range(self.nspins):
            kpt1_q = [KPoint(self.fullkd, kpt) for kpt in kpt_u if kpt.s == s]
            kpt2_q = kpt1_q[:]

            if len(kpt1_q) == 0:
                # No s-spins on this CPU:
                continue

            # Send rank:
            srank = self.fullkd.get_rank_and_index(s, (kpt1_q[0].k - 1) % K)[0]

            # Receive rank:
            rrank = self.fullkd.get_rank_and_index(s, (kpt1_q[-1].k + 1) % K)[0]

            # Shift k-points K // 2 times:
            for i in range(K // 2 + 1):
                if i < K // 2:
                    if parallel:
                        kpt = kpt2_q[-1].next()
                        kpt.start_receiving(rrank)
                        kpt2_q[0].start_sending(srank)
                    else:
                        kpt = kpt2_q[0]

                for kpt1, kpt2 in zip(kpt1_q, kpt2_q):
                    if 2 * i == K:
                        self.apply(kpt1, kpt2, invert=(kpt1.k > kpt2.k))
                    else:
                        self.apply(kpt1, kpt2)
                        self.apply(kpt1, kpt2, invert=True)

                if i < K // 2:
                    if parallel:
                        kpt.wait()
                        kpt2_q[0].wait()
                    kpt2_q.pop(0)
                    kpt2_q.append(kpt)
            
        self.exx = world.sum(self.exx)
        world.sum(self.exx_skn)
        self.exx += self.calculate_paw_correction()
        
    def apply(self, kpt1, kpt2, invert=False):
        #print world.rank,kpt1.k,kpt2.k,invert
        k1_c = self.fullkd.ibzk_kc[kpt1.k]
        k2_c = self.fullkd.ibzk_kc[kpt2.k]
        if invert:
            k2_c = -k2_c
        k12_c = k1_c - k2_c
        N_c = self.gd.N_c
        eikr_R = np.exp(2j * pi * np.dot(np.indices(N_c).T, k12_c / N_c).T)

        for q, k_c in enumerate(self.bzk_kc):
            if abs(k_c + k12_c).max() < 1e-9:
                q0 = q
                break

        for q, k_c in enumerate(self.bzk_kc):
            if abs(k_c - k12_c).max() < 1e-9:
                q00 = q
                break

        Gpk2_G = self.pwd.G2_qG[q0]
        if Gpk2_G[0] == 0:
            Gpk2_G = Gpk2_G.copy()
            Gpk2_G[0] = 1.0 / self.gamma

        N = N_c.prod()
        vol = self.gd.dv * N
        nspins = self.nspins

        same = (kpt1.k == kpt2.k)
        
        for n1, psit1_R in enumerate(kpt1.psit_nG):
            f1 = kpt1.f_n[n1]
            for n2, psit2_R in enumerate(kpt2.psit_nG):
                if same and n2 > n1:
                    continue
                
                f2 = kpt2.f_n[n2]

                nt_R = self.calculate_pair_density(n1, n2, kpt1, kpt2, q0,
                                                   invert)
                                                   
                nt_G = self.pwd.fft(nt_R * eikr_R) / N
                vt_G = nt_G.copy()
                vt_G *= -pi * vol / Gpk2_G
                e = np.vdot(nt_G, vt_G).real * nspins * self.hybrid
                if same and n1 == n2:
                    e /= 2
                    
                self.exx += e * f1 * f2
                self.ekin -= 2 * e * f1 * f2
                self.exx_skn[kpt1.s, kpt1.k, n1] += f2 * e
                self.exx_skn[kpt2.s, kpt2.k, n2] += f1 * e

                calculate_potential = not True
                if calculate_potential:
                    vt_R = self.pwd.ifft(vt_G).conj() * eikr_R * N / vol
                    if kpt1 is kpt2 and not invert and n1 == n2:
                        kpt1.vt_nG[n1] = 0.5 * f1 * vt_R

                    if invert:
                        kpt1.Htpsit_nG[n1] += \
                                           f2 * nspins * psit2_R.conj() * vt_R
                    else:
                        kpt1.Htpsit_nG[n1] += f2 * nspins * psit2_R * vt_R

                    if kpt1 is not kpt2:
                        if invert:
                            kpt2.Htpsit_nG[n2] += (f1 * nspins *
                                                   psit1_R.conj() * vt_R)
                        else:
                            kpt2.Htpsit_nG[n2] += (f1 * nspins *
                                                   psit1_R * vt_R.conj())

    def calculate_paw_correction(self):
        exx = 0
        deg = 2 // self.nspins  # spin degeneracy
        for a, D_sp in self.density.D_asp.items():
            setup = self.setups[a]
            for D_p in D_sp:
                D_ii = unpack2(D_p)
                ni = len(D_ii)

                for i1 in range(ni):
                    for i2 in range(ni):
                        A = 0.0
                        for i3 in range(ni):
                            p13 = packed_index(i1, i3, ni)
                            for i4 in range(ni):
                                p24 = packed_index(i2, i4, ni)
                                A += setup.M_pp[p13, p24] * D_ii[i3, i4]
                        p12 = packed_index(i1, i2, ni)
                        exx -= self.hybrid / deg * D_ii[i1, i2] * A

                if setup.X_p is not None:
                    exx -= self.hybrid * np.dot(D_p, setup.X_p)
            exx += self.hybrid * setup.ExxC
        return exx
    
    def calculate_pair_density(self, n1, n2, kpt1, kpt2, q, invert):
        if invert:
            nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2].conj()
        else:
            nt_G = kpt1.psit_nG[n1].conj() * kpt2.psit_nG[n2]

        Q_aL = {}
        for a, P1_ni in kpt1.P_ani.items():
            P1_i = P1_ni[n1]
            P2_i = kpt2.P_ani[a][n2]
            if invert:
                D_ii = np.outer(P1_i.conj(), P2_i.conj())
            else:
                D_ii = np.outer(P1_i.conj(), P2_i)
            D_p = pack(D_ii)
            Q_aL[a] = np.dot(D_p, self.setups[a].Delta_pL)

        self.ghat.add(nt_G, Q_aL, q)
        return nt_G