Exemplo n.º 1
0
    def set_log(self, log=None):
        """Set output log."""

        if log is None:
            self.timer = nulltimer
        elif log == '-':
            self.timer = StepTimer(name='elph')
        else:
            self.timer = StepTimer(name='elph', out=open(log, 'w'))
Exemplo n.º 2
0
    def set_log(self, log=None):
        """Set output log."""

        if log is None:
            self.timer = nulltimer
        elif log == '-':
            self.timer = StepTimer(name='elph')
        else:
            self.timer = StepTimer(name='elph', out=open(log, 'w'))
Exemplo n.º 3
0
    def get_M(self, modes, log=None, q=0):
        """Calculate el-ph coupling matrix for given modes(s).

        XXX:
        kwarg "q=0" is an ugly hack for k-points.
        There shuold be loops over q!

        Note that modes must be given as a dictionary with mode
        frequencies in eV and corresponding mode vectors in units
        of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit.
        In short frequencies and mode vectors must be given in ase units.

        ::

                  d                   d  ~
            < w | -- v | w' > = < w | -- v | w'>
                  dP                  dP

                               _
                              \        ~a     d   .       ~a
                            +  ) < w | p  >   -- /_\H   < p | w' >
                              /_        i     dP     ij    j
                              a,ij

                               _
                              \        d  ~a     .        ~a
                            +  ) < w | -- p  >  /_\H    < p | w' >
                              /_       dP  i        ij     j
                              a,ij

                               _
                              \        ~a     .        d  ~a
                            +  ) < w | p  >  /_\H    < -- p  | w' >
                              /_        i        ij    dP  j
                              a,ij

        """
        if log is None:
            timer = nulltimer
        elif log == '-':
            timer = StepTimer(name='EPCM')
        else:
            timer = StepTimer(name='EPCM', out=open(log, 'w'))

        modes1 = modes.copy()
        #convert to atomic units
        amu = 1.6605402e-27  # atomic unit mass [Kg]
        me = 9.1093897e-31  # electron mass    [Kg]
        modes = {}
        for k in modes1.keys():
            modes[k / Hartree] = modes1[k] / np.sqrt(amu / me)

        dvt_Gx, ddH_aspx = self.get_gradient()

        from gpaw import restart
        atoms, calc = restart('eq.gpw', txt=None)
        if calc.wfs.S_qMM is None:
            calc.initialize(atoms)
            calc.initialize_positions(atoms)

        wfs = calc.wfs
        nao = wfs.setups.nao
        bfs = wfs.basis_functions
        dtype = wfs.dtype
        spin = 0  # XXX

        M_lii = {}
        timer.write_now('Starting gradient of pseudo part')
        for f, mode in modes.items():
            mo = []
            M_ii = np.zeros((nao, nao), dtype)
            for a in self.indices:
                mo.append(mode[a])
            mode = np.asarray(mo).flatten()
            dvtdP_G = np.dot(dvt_Gx, mode)
            bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q)
            tri2full(M_ii, 'L')
            M_lii[f] = M_ii
        timer.write_now('Finished gradient of pseudo part')

        P_aqMi = calc.wfs.P_aqMi
        # Add the term
        #  _
        # \        ~a     d   .       ~a
        #  ) < w | p  >   -- /_\H   < p | w' >
        # /_        i     dP     ij    j
        # a,ij

        Ma_lii = {}
        for f, mode in modes.items():
            Ma_lii[f] = np.zeros_like(M_lii.values()[0])

        timer.write_now('Starting gradient of dH^a part')
        for f, mode in modes.items():
            mo = []
            for a in self.indices:
                mo.append(mode[a])
            mode = np.asarray(mo).flatten()

            for a, ddH_spx in ddH_aspx.items():
                ddHdP_sp = np.dot(ddH_spx, mode)
                ddHdP_ii = unpack2(ddHdP_sp[spin])
                Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T)
        timer.write_now('Finished gradient of dH^a part')

        timer.write_now('Starting gradient of projectors part')
        dP_aMix = self.get_dP_aMix(calc.spos_ac, wfs, q, timer)
        timer.write_now('Finished gradient of projectors part')

        dH_asp = pickle.load(open('v.eq.pckl', 'rb'))[1]

        Mb_lii = {}
        for f, mode in modes.items():
            Mb_lii[f] = np.zeros_like(M_lii.values()[0])

        for f, mode in modes.items():
            for a, dP_Mix in dP_aMix.items():
                dPdP_Mi = np.dot(dP_Mix, mode[a])
                dH_ii = unpack2(dH_asp[a][spin])
                dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T)
                Mb_lii[f] -= dPdP_MM + dPdP_MM.T
                # XXX The minus sign here is quite subtle.
                # It is related to how the derivative of projector
                # functions in GPAW is calculated.
                # More thorough explanations, anyone...?

        # Units of M_lii are Hartree/(Bohr * sqrt(m_e))
        for mode in M_lii.keys():
            M_lii[mode] += Ma_lii[mode] + Mb_lii[mode]

        # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f)
        # has units Bohr * sqrt(me)
        M_lii_1 = M_lii.copy()
        M_lii = {}

        for f in M_lii_1.keys():
            M_lii[f * Hartree] = M_lii_1[f] * Hartree / np.sqrt(2 * f)

        return M_lii
Exemplo n.º 4
0
    def get_M(self, modes, log=None, q=0):
        """Calculate el-ph coupling matrix for given modes(s).

        XXX:
        kwarg "q=0" is an ugly hack for k-points.
        There shuold be loops over q!

        Note that modes must be given as a dictionary with mode
        frequencies in eV and corresponding mode vectors in units
        of 1/sqrt(amu), where amu = 1.6605402e-27 Kg is an atomic mass unit.
        In short frequencies and mode vectors must be given in ase units.

        ::
        
                  d                   d  ~
            < w | -- v | w' > = < w | -- v | w'>
                  dP                  dP

                               _
                              \        ~a     d   .       ~a
                            +  ) < w | p  >   -- /_\H   < p | w' >
                              /_        i     dP     ij    j
                              a,ij

                               _
                              \        d  ~a     .        ~a
                            +  ) < w | -- p  >  /_\H    < p | w' >
                              /_       dP  i        ij     j
                              a,ij

                               _
                              \        ~a     .        d  ~a
                            +  ) < w | p  >  /_\H    < -- p  | w' >
                              /_        i        ij    dP  j
                              a,ij

        """
        if log is None:
            timer = nulltimer
        elif log == '-':
            timer = StepTimer(name='EPCM')
        else:
            timer = StepTimer(name='EPCM', out=open(log, 'w'))

        modes1 = modes.copy()
        #convert to atomic units
        amu = 1.6605402e-27 # atomic unit mass [Kg]
        me = 9.1093897e-31  # electron mass    [Kg]
        modes = {}
        for k in modes1.keys():        
            modes[k / Hartree] = modes1[k] / np.sqrt(amu / me)

        dvt_Gx, ddH_aspx = self.get_gradient()

        from gpaw import restart
        atoms, calc = restart('eq.gpw', txt=None)
        spos_ac = atoms.get_scaled_positions()
        if calc.wfs.S_qMM is None:
            calc.initialize(atoms)
            calc.initialize_positions(atoms)

        wfs = calc.wfs
        nao = wfs.setups.nao
        bfs = wfs.basis_functions
        dtype = wfs.dtype
        spin = 0 # XXX

        M_lii = {}
        timer.write_now('Starting gradient of pseudo part')
        for f, mode in modes.items():
            mo = []    
            M_ii = np.zeros((nao, nao), dtype)
            for a in self.indices:
                mo.append(mode[a])
            mode = np.asarray(mo).flatten()
            dvtdP_G = np.dot(dvt_Gx, mode)   
            bfs.calculate_potential_matrix(dvtdP_G, M_ii, q=q)
            tri2full(M_ii, 'L')
            M_lii[f] = M_ii               
        timer.write_now('Finished gradient of pseudo part')

        P_aqMi = calc.wfs.P_aqMi
        # Add the term
        #  _
        # \        ~a     d   .       ~a
        #  ) < w | p  >   -- /_\H   < p | w' >
        # /_        i     dP     ij    j
        # a,ij

        Ma_lii = {}
        for f, mode in modes.items():
            Ma_lii[f] = np.zeros_like(M_lii.values()[0])
        
        timer.write_now('Starting gradient of dH^a part')
        for f, mode in modes.items():
            mo = []
            for a in self.indices:
                mo.append(mode[a])
            mode = np.asarray(mo).flatten()
            
            for a, ddH_spx in ddH_aspx.items():
                ddHdP_sp = np.dot(ddH_spx, mode)
                ddHdP_ii = unpack2(ddHdP_sp[spin])
                Ma_lii[f] += dots(P_aqMi[a][q], ddHdP_ii, P_aqMi[a][q].T)
        timer.write_now('Finished gradient of dH^a part')

        timer.write_now('Starting gradient of projectors part')
        spos_ac = self.atoms.get_scaled_positions() % 1.0
        dP_aMix = self.get_dP_aMix(spos_ac, wfs, q, timer)
        timer.write_now('Finished gradient of projectors part')

        dH_asp = pickle.load(open('v.eq.pckl'))[1]
        
        Mb_lii = {}
        for f, mode in modes.items():
            Mb_lii[f] = np.zeros_like(M_lii.values()[0])

        for f, mode in modes.items():
            for a, dP_Mix in dP_aMix.items():
                dPdP_Mi = np.dot(dP_Mix, mode[a])
                dH_ii = unpack2(dH_asp[a][spin])    
                dPdP_MM = dots(dPdP_Mi, dH_ii, P_aqMi[a][q].T)
                Mb_lii[f] -= dPdP_MM + dPdP_MM.T 
                # XXX The minus sign here is quite subtle.
                # It is related to how the derivative of projector
                # functions in GPAW is calculated.
                # More thorough explanations, anyone...?
                
        # Units of M_lii are Hartree/(Bohr * sqrt(m_e))
        for mode in M_lii.keys():
            M_lii[mode] += Ma_lii[mode] + Mb_lii[mode]

        # conversion to eV. The prefactor 1 / sqrt(hb^2 / 2 * hb * f)
        # has units Bohr * sqrt(me)
        M_lii_1 = M_lii.copy()
        M_lii = {}

        for f in M_lii_1.keys():
            M_lii[f * Hartree] =  M_lii_1[f] * Hartree / np.sqrt(2 * f)

        return M_lii
Exemplo n.º 5
0
class ElectronPhononCoupling(Displacement):
    """Class for calculating the electron-phonon coupling in an LCAO basis.

    The derivative of the effective potential wrt atomic displacements is
    obtained from a finite difference approximation to the derivative by doing
    a self-consistent calculation for atomic displacements in the +/-
    directions. These calculations are carried out in the ``run`` member
    function.

    The subsequent calculation of the coupling matrix in the basis of atomic
    orbitals (or Bloch-sums hereof for periodic systems) is handled by the
    ``calculate_matrix`` member function. 
   
    """
    def __init__(self,
                 atoms,
                 calc=None,
                 supercell=(1, 1, 1),
                 name='elph',
                 delta=0.01,
                 phonons=None):
        """Initialize with base class args and kwargs.

        Parameters
        ----------
        atoms: Atoms object
            The atoms to work on.
        calc: Calculator
            Calculator for the supercell calculation.
        supercell: tuple
            Size of supercell given by the number of repetitions (l, m, n) of
            the small unit cell in each direction.
        name: str
            Name to use for files (default: 'elph').
        delta: float
            Magnitude of displacements.
        phonons: Phonons object
            If provided, the ``__call__`` member function of the ``Phonons``
            class in ASE will be called in the ``__call__`` member function of
            this class.
            
        """

        # Init base class and make the center cell in the supercell the
        # reference cell
        Displacement.__init__(self,
                              atoms,
                              calc=calc,
                              supercell=supercell,
                              name=name,
                              delta=delta,
                              refcell='center')

        # Store ``Phonons`` object in attribute
        self.phonons = phonons
        # Log
        self.set_log()
        # LCAO calculator
        self.calc_lcao = None
        # Supercell matrix
        self.g_xNNMM = None

    def __call__(self, atoms_N):
        """Extract effective potential and projector coefficients."""

        # Do calculation
        atoms_N.get_potential_energy()

        # Call phonons class if provided
        if self.phonons is not None:
            forces = self.phonons.__call__(atoms_N)

        # Get calculator
        calc = atoms_N.get_calculator()

        # Effective potential (in Hartree) and projector coefficients
        Vt_G = calc.hamiltonian.vt_sG[0]
        Vt_G = calc.wfs.gd.collect(Vt_G, broadcast=True)
        dH_asp = calc.hamiltonian.dH_asp

        setups = calc.wfs.setups
        nspins = calc.wfs.nspins
        gd_comm = calc.wfs.gd.comm

        dH_all_asp = {}
        for a, setup in enumerate(setups):
            ni = setup.ni
            nii = ni * (ni + 1) // 2
            dH_tmp_sp = np.zeros((nspins, nii))
            if a in dH_asp:
                dH_tmp_sp[:] = dH_asp[a]
            gd_comm.sum(dH_tmp_sp)
            dH_all_asp[a] = dH_tmp_sp

        return Vt_G, dH_all_asp

    def set_lcao_calculator(self, calc):
        """Set LCAO calculator for the calculation of the supercell matrix."""

        # Add parameter checks here
        # - check that gamma
        # - check that no symmetries are used
        # - ...
        assert calc.input_parameters['mode'] == 'lcao', "LCAO mode required."
        symmetry = calc.input_parameters['symmetry']
        assert symmetry['point_group'] != True, "Symmetries not supported."

        self.calc_lcao = calc

    def set_basis_info(self, *args):
        """Store lcao basis info for atoms in reference cell in attribute.

        Parameters
        ----------
        args: tuple
            If the LCAO calculator is not available (e.g. if the supercell is
            loaded from file), the ``load_supercell_matrix`` member function
            provides the required info as arguments.
            
        """

        if len(args) == 0:
            calc = self.calc_lcao
            setups = calc.wfs.setups
            bfs = calc.wfs.basis_functions
            nao_a = [setups[a].nao for a in range(len(self.atoms))]
            M_a = [bfs.M_a[a] for a in range(len(self.atoms))]
        else:
            M_a = args[0]
            nao_a = args[1]

        self.basis_info = {'M_a': M_a, 'nao_a': nao_a}

    def set_log(self, log=None):
        """Set output log."""

        if log is None:
            self.timer = nulltimer
        elif log == '-':
            self.timer = StepTimer(name='elph')
        else:
            self.timer = StepTimer(name='elph', out=open(log, 'w'))

    def calculate_supercell_matrix(self,
                                   dump=0,
                                   name=None,
                                   filter=None,
                                   include_pseudo=True,
                                   atoms=None):
        """Calculate matrix elements of the el-ph coupling in the LCAO basis.

        This function calculates the matrix elements between LCAOs and local
        atomic gradients of the effective potential. The matrix elements are
        calculated for the supercell used to obtain finite-difference
        approximations to the derivatives of the effective potential wrt to
        atomic displacements.

        Parameters
        ----------
        dump: int
            Dump supercell matrix to pickle file (default: 0).

            0: Supercell matrix not saved

            1: Supercell matrix saved in a single pickle file.

            2: Dump matrix for different gradients in separate files. Useful
               for large systems where the total array gets too large for a
               single pickle file.

        name: string
            User specified name of the generated pickle file(s). If not
            provided, the string in the ``name`` attribute is used.
        filter: str
            Fourier filter atomic gradients of the effective potential. The
            specified components (``normal`` or ``umklapp``) are removed
            (default: None).
        include_pseudo: bool
            Include the contribution from the psedupotential in the atomic
            gradients. If ``False``, only the gradient of the effective
            potential is included (default: True).
        atoms: Atoms object
            Calculate supercell for an ``Atoms`` object different from the one
            provided in the ``__init__`` method (WARNING, NOT working!).
            
        """

        assert self.calc_lcao is not None, "Set LCAO calculator"

        # Supercell atoms
        if atoms is None:
            atoms_N = self.atoms * self.N_c
        else:
            atoms_N = atoms

        # Initialize calculator if required and extract useful quantities
        calc = self.calc_lcao
        if not hasattr(calc.wfs, 'S_qMM'):
            calc.initialize(atoms_N)
            calc.initialize_positions(atoms_N)
        self.set_basis_info()
        basis = calc.input_parameters['basis']

        # Extract useful objects from the calculator
        wfs = calc.wfs
        gd = calc.wfs.gd
        kd = calc.wfs.kd
        kpt_u = wfs.kpt_u
        setups = wfs.setups
        nao = setups.nao
        bfs = wfs.basis_functions
        dtype = wfs.dtype
        spin = 0  # XXX

        # If gamma calculation, overlap with neighboring cell cannot be removed
        if kd.gamma:
            print("WARNING: Gamma-point calculation.")
        else:
            # Bloch to real-space converter
            tb = TightBinding(atoms_N, calc)

        self.timer.write_now("Calculating supercell matrix")

        self.timer.write_now("Calculating real-space gradients")
        # Calculate finite-difference gradients (in Hartree / Bohr)
        V1t_xG, dH1_xasp = self.calculate_gradient()
        self.timer.write_now("Finished real-space gradients")

        # Fourier filter the atomic gradients of the effective potential
        if filter is not None:
            self.timer.write_now("Fourier filtering gradients")
            V1_xG = V1t_xG.copy()
            self.fourier_filter(V1t_xG, components=filter)
            self.timer.write_now("Finished Fourier filtering")

        # For the contribution from the derivative of the projectors
        dP_aqvMi = self.calculate_dP_aqvMi(wfs)
        # Equilibrium atomic Hamiltonian matrix (projector coefficients)
        dH_asp = pickle.load(open(self.name + '.eq.pckl'))[1]

        # Check that the grid is the same as in the calculator
        assert np.all(V1t_xG.shape[-3:] == (gd.N_c + gd.pbc_c - 1)), \
               "Mismatch in grids."

        # Calculate < i k | grad H | j k >, i.e. matrix elements in Bloch basis
        # List for supercell matrices;
        g_xNNMM = []
        self.timer.write_now("Calculating gradient of PAW Hamiltonian")

        # Do each cartesian component separately
        for i, a in enumerate(self.indices):
            for v in range(3):

                # Corresponding array index
                x = 3 * i + v
                V1t_G = V1t_xG[x]
                self.timer.write_now("%s-gradient of atom %u" %
                                     (['x', 'y', 'z'][v], a))

                # Array for different k-point components
                g_qMM = np.zeros((len(kpt_u), nao, nao), dtype)

                # 1) Gradient of effective potential
                self.timer.write_now(
                    "Starting gradient of effective potential")
                for kpt in kpt_u:
                    # Matrix elements
                    geff_MM = np.zeros((nao, nao), dtype)
                    bfs.calculate_potential_matrix(V1t_G, geff_MM, q=kpt.q)
                    tri2full(geff_MM, 'L')
                    # Insert in array
                    g_qMM[kpt.q] += geff_MM

                self.timer.write_now(
                    "Finished gradient of effective potential")

                if include_pseudo:
                    self.timer.write_now("Starting gradient of pseudo part")

                    # 2) Gradient of non-local part (projectors)
                    self.timer.write_now("Starting gradient of dH^a")
                    P_aqMi = calc.wfs.P_aqMi
                    # 2a) dH^a part has contributions from all other atoms
                    for kpt in kpt_u:
                        # Matrix elements
                        gp_MM = np.zeros((nao, nao), dtype)
                        dH1_asp = dH1_xasp[x]
                        for a_, dH1_sp in dH1_asp.items():
                            dH1_ii = unpack2(dH1_sp[spin])
                            gp_MM += np.dot(
                                P_aqMi[a_][kpt.q],
                                np.dot(dH1_ii,
                                       P_aqMi[a_][kpt.q].T.conjugate()))
                        g_qMM[kpt.q] += gp_MM
                    self.timer.write_now("Finished gradient of dH^a")

                    self.timer.write_now("Starting gradient of projectors")
                    # 2b) dP^a part has only contributions from the same atoms
                    dP_qvMi = dP_aqvMi[a]
                    dH_ii = unpack2(dH_asp[a][spin])
                    for kpt in kpt_u:
                        #XXX Sort out the sign here; conclusion -> sign = +1 !
                        P1HP_MM = +1 * np.dot(
                            dP_qvMi[kpt.q][v],
                            np.dot(dH_ii, P_aqMi[a][kpt.q].T.conjugate()))
                        # Matrix elements
                        gp_MM = P1HP_MM + P1HP_MM.T.conjugate()
                        g_qMM[kpt.q] += gp_MM
                    self.timer.write_now("Finished gradient of projectors")
                    self.timer.write_now("Finished gradient of pseudo part")

                # Extract R_c=(0, 0, 0) block by Fourier transforming
                if kd.gamma or kd.N_c is None:
                    g_MM = g_qMM[0]
                else:
                    # Convert to array
                    g_MM = tb.bloch_to_real_space(g_qMM, R_c=(0, 0, 0))[0]

                # Reshape to global unit cell indices
                N = np.prod(self.N_c)
                # Number of basis function in the primitive cell
                assert (nao % N) == 0, "Alarm ...!"
                nao_cell = nao / N
                g_NMNM = g_MM.reshape((N, nao_cell, N, nao_cell))
                g_NNMM = g_NMNM.swapaxes(1, 2).copy()
                self.timer.write_now("Finished supercell matrix")

                if dump != 2:
                    g_xNNMM.append(g_NNMM)
                else:
                    if name is not None:
                        fname = '%s.supercell_matrix_x_%2.2u.%s.pckl' % (
                            name, x, basis)
                    else:
                        fname = self.name + \
                                '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis)
                    if kd.comm.rank == 0:
                        fd = open(fname, 'w')
                        M_a = self.basis_info['M_a']
                        nao_a = self.basis_info['nao_a']
                        pickle.dump((g_NNMM, M_a, nao_a), fd, 2)
                        fd.close()

        self.timer.write_now("Finished gradient of PAW Hamiltonian")

        if dump != 2:
            # Collect gradients in one array
            self.g_xNNMM = np.array(g_xNNMM)

            # Dump to pickle file using binary mode together with basis info
            if dump and kd.comm.rank == 0:
                if name is not None:
                    fname = '%s.supercell_matrix.%s.pckl' % (name, basis)
                else:
                    fname = self.name + '.supercell_matrix.%s.pckl' % basis
                fd = open(fname, 'w')
                M_a = self.basis_info['M_a']
                nao_a = self.basis_info['nao_a']
                pickle.dump((self.g_xNNMM, M_a, nao_a), fd, 2)
                fd.close()

    def load_supercell_matrix(self, basis=None, name=None, multiple=False):
        """Load supercell matrix from pickle file.

        Parameters
        ----------
        basis: string
            String specifying the LCAO basis used to calculate the supercell
            matrix, e.g. 'dz(dzp)'.
        name: string
            User specified name of the pickle file.
        multiple: bool
            Load each derivative from individual files.
            
        """

        assert (basis is not None) or (name is not None), \
               "Provide basis or name."

        if self.g_xNNMM is not None:
            self.g_xNNMM = None

        if not multiple:
            # File name
            if name is not None:
                fname = name
            else:
                fname = self.name + '.supercell_matrix.%s.pckl' % basis
            fd = open(fname)
            self.g_xNNMM, M_a, nao_a = pickle.load(fd)
            fd.close()
        else:
            g_xNNMM = []
            for x in range(len(self.indices) * 3):
                if name is not None:
                    fname = name
                else:
                    fname = self.name + \
                            '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis)
                fd = open(fname, 'r')
                g_NNMM, M_a, nao_a = pickle.load(fd)
                fd.close()
                g_xNNMM.append(g_NNMM)
            self.g_xNNMM = np.array(g_xNNMM)

        self.set_basis_info(M_a, nao_a)

    def apply_cutoff(self, cutmax=None, cutmin=None):
        """Zero matrix element inside/beyond the specified cutoffs.

        Parameters
        ----------
        cutmax: float
            Zero matrix elements for basis functions with a distance to the
            atomic gradient that is larger than the cutoff.
        cutmin: float
            Zero matrix elements where both basis functions have distances to
            the atomic gradient that is smaller than the cutoff.

        """

        if cutmax is not None:
            cutmax = float(cutmax)
        if cutmin is not None:
            cutmin = float(cutmin)

        # Reference to supercell matrix attribute
        g_xNNMM = self.g_xNNMM

        # Number of atoms and primitive cells
        N_atoms = len(self.indices)
        N = np.prod(self.N_c)
        nao = g_xNNMM.shape[-1]

        # Reshape array
        g_avNNMM = g_xNNMM.reshape(N_atoms, 3, N, N, nao, nao)

        # Make slices for orbitals on atoms
        M_a = self.basis_info['M_a']
        nao_a = self.basis_info['nao_a']
        slice_a = []
        for a in range(len(self.atoms)):
            start = M_a[a]
            stop = start + nao_a[a]
            s = slice(start, stop)
            slice_a.append(s)

        # Lattice vectors
        R_cN = self.lattice_vectors()

        # Unit cell vectors
        cell_vc = self.atoms.cell.transpose()
        # Atomic positions in reference cell
        pos_av = self.atoms.get_positions()

        # Create a mask array to zero the relevant matrix elements
        if cutmin is not None:
            mask_avNNMM = np.zeros(g_avNNMM.shape, dtype=bool)

        # Zero elements where one of the basis orbitals has a distance to atoms
        # (atomic gradients) in the reference cell larger than the cutoff
        for n in range(N):
            # Lattice vector to cell
            R_v = np.dot(cell_vc, R_cN[:, n])
            # Atomic positions in cell
            posn_av = pos_av + R_v
            for i, a in enumerate(self.indices):
                # Atomic distances wrt to the position of the gradient
                dist_a = np.sqrt(np.sum((pos_av[a] - posn_av)**2, axis=-1))

                if cutmax is not None:
                    # Atoms indices where the distance is larger than the max
                    # cufoff
                    j_a = np.where(dist_a > cutmax)[0]
                    # Zero elements
                    for j in j_a:
                        g_avNNMM[a, :, n, :, slice_a[j], :] = 0.0
                        g_avNNMM[a, :, :, n, :, slice_a[j]] = 0.0

                if cutmin is not None:
                    # Atoms indices where the distance is larger than the min
                    # cufoff
                    j_a = np.where(dist_a > cutmin)[0]
                    # Update mask to keep elements where one LCAO is outside
                    # the min cutoff
                    for j in j_a:
                        mask_avNNMM[a, :, n, :, slice_a[j], :] = True
                        mask_avNNMM[a, :, :, n, :, slice_a[j]] = True

        # Zero elements where both LCAOs are located within the min cutoff
        if cutmin is not None:
            g_avNNMM[~mask_avNNMM] = 0.0

    def lcao_matrix(self, u_l, omega_l):
        """Calculate the el-ph coupling in the electronic LCAO basis.
        
        For now, only works for Gamma-point phonons.
        
        Parameters
        ----------
        u_l: ndarray
            Mass-scaled polarization vectors (in units of 1 / sqrt(amu)) of the
            phonons.
        omega_l: ndarray
            Vibrational frequencies in eV. 
            
        """

        # Supercell matrix (Hartree / Bohr)
        assert self.g_xNNMM is not None, "Load supercell matrix."
        assert self.g_xNNMM.shape[1:3] == (1, 1)
        g_xMM = self.g_xNNMM[:, 0, 0, :, :]
        # Number of atomic orbitals
        nao = g_xMM.shape[-1]
        # Number of phonon modes
        nmodes = u_l.shape[0]

        #
        u_lx = u_l.reshape(nmodes, 3 * len(self.atoms))
        g_lMM = np.dot(u_lx, g_xMM.transpose(1, 0, 2))

        # Multiply prefactor sqrt(hbar / 2 * M * omega) in units of Bohr
        amu = units._amu  # atomic mass unit
        me = units._me  # electron mass
        g_lMM /= np.sqrt(2 * amu / me / units.Hartree * \
                         omega_l[:, np.newaxis, np.newaxis])
        # Convert to eV
        g_lMM *= units.Hartree

        return g_lMM

    def bloch_matrix(self,
                     kpts,
                     qpts,
                     c_kn,
                     u_ql,
                     omega_ql=None,
                     kpts_from=None):
        """Calculate el-ph coupling in the Bloch basis for the electrons.

        This function calculates the electron-phonon coupling between the
        specified Bloch states, i.e.::

                      ______ 
            mnl      / hbar               ^
           g    =   /-------  < m k + q | e  . grad V  | n k >
            kq    \/ 2 M w                 ql        q
                          ql

        In case the ``omega_ql`` keyword argument is not given, the bare matrix
        element (in units of eV / Ang) without the sqrt prefactor is returned. 
        
        Parameters
        ----------
        kpts: ndarray or tuple.
            k-vectors of the Bloch states. When a tuple of integers is given, a
            Monkhorst-Pack grid with the specified number of k-points along the
            directions of the reciprocal lattice vectors is generated.
        qpts: ndarray or tuple.
            q-vectors of the phonons.
        c_kn: ndarray
            Expansion coefficients for the Bloch states. The ordering must be
            the same as in the ``kpts`` argument.
        u_ql: ndarray
            Mass-scaled polarization vectors (in units of 1 / sqrt(amu)) of the
            phonons. Again, the ordering must be the same as in the
            corresponding ``qpts`` argument.
        omega_ql: ndarray
            Vibrational frequencies in eV. 
        kpts_from: list of ints or int
            Calculate only the matrix element for the k-vectors specified by
            their index in the ``kpts`` argument (default: all).

        In short, phonon frequencies and mode vectors must be given in ase units.

        """

        assert self.g_xNNMM is not None, "Load supercell matrix."
        assert len(c_kn.shape) == 3
        assert len(u_ql.shape) == 4
        if omega_ql is not None:
            assert np.all(u_ql.shape[:2] == omega_ql.shape[:2])

        # Translate k-points into 1. BZ (required by ``find_k_plus_q``` member
        # function of the ```KPointDescriptor``).
        if isinstance(kpts, np.ndarray):
            assert kpts.shape[1] == 3, "kpts_kc array must be given"
            # XXX This does not seem to cause problems!
            kpts -= kpts.round()

        # Use the KPointDescriptor to keep track of the k and q-vectors
        kd_kpts = KPointDescriptor(kpts)
        kd_qpts = KPointDescriptor(qpts)
        # Check that number of k- and q-points agree with the number of Bloch
        # functions and polarization vectors
        assert kd_kpts.nbzkpts == len(c_kn)
        assert kd_qpts.nbzkpts == len(u_ql)

        # Include all k-point per default
        if kpts_from is None:
            kpts_kc = kd_kpts.bzk_kc
            kpts_k = range(kd_kpts.nbzkpts)
        else:
            kpts_kc = kd_kpts.bzk_kc[kpts_from]
            if isinstance(kpts_from, int):
                kpts_k = list([kpts_from])
            else:
                kpts_k = list(kpts_from)

        # Supercell matrix (real matrix in Hartree / Bohr)
        g_xNNMM = self.g_xNNMM

        # Number of phonon modes and electronic bands
        nmodes = u_ql.shape[1]
        nbands = c_kn.shape[1]
        # Number of atoms displacements and basis functions
        ndisp = np.prod(u_ql.shape[2:])
        assert ndisp == (3 * len(self.indices))
        nao = c_kn.shape[2]
        assert ndisp == g_xNNMM.shape[0]
        assert nao == g_xNNMM.shape[-1]

        # Lattice vectors
        R_cN = self.lattice_vectors()
        # Number of unit cell in supercell
        N = np.prod(self.N_c)

        # Allocate array for couplings
        g_qklnn = np.zeros(
            (kd_qpts.nbzkpts, len(kpts_kc), nmodes, nbands, nbands),
            dtype=complex)

        self.timer.write_now("Calculating coupling matrix elements")
        for q, q_c in enumerate(kd_qpts.bzk_kc):

            # Find indices of k+q for the k-points
            kplusq_k = kd_kpts.find_k_plus_q(q_c, kpts_k=kpts_k)

            # Here, ``i`` is counting from 0 and ``k`` is the global index of
            # the k-point
            for i, (k, k_c) in enumerate(zip(kpts_k, kpts_kc)):

                # Check the wave vectors (adapted to the ``KPointDescriptor`` class)
                kplusq_c = k_c + q_c
                kplusq_c -= kplusq_c.round()
                assert np.allclose(kplusq_c, kd_kpts.bzk_kc[kplusq_k[i]] ), \
                       (i, k, k_c, q_c, kd_kpts.bzk_kc[kplusq_k[i]])

                # Allocate array
                g_xMM = np.zeros((ndisp, nao, nao), dtype=complex)

                # Multiply phase factors
                for m in range(N):
                    for n in range(N):
                        Rm_c = R_cN[:, m]
                        Rn_c = R_cN[:, n]
                        phase = np.exp(
                            2.j * pi *
                            (np.dot(k_c, Rm_c - Rn_c) + np.dot(q_c, Rm_c)))
                        # Sum contributions from different cells
                        g_xMM += g_xNNMM[:, m, n, :, :] * phase

                # LCAO coefficient for Bloch states
                ck_nM = c_kn[k]
                ckplusq_nM = c_kn[kplusq_k[i]]
                # Mass scaled polarization vectors
                u_lx = u_ql[q].reshape(nmodes, 3 * len(self.atoms))

                g_nxn = np.dot(ckplusq_nM.conj(), np.dot(g_xMM, ck_nM.T))
                g_lnn = np.dot(u_lx, g_nxn)

                # Insert value
                g_qklnn[q, i] = g_lnn

                # XXX Temp
                if np.all(q_c == 0.0):
                    # These should be real
                    print(g_qklnn[q].imag.min(), g_qklnn[q].imag.max())

        self.timer.write_now(
            "Finished calculation of coupling matrix elements")

        # Return the bare matrix element if frequencies are not given
        if omega_ql is None:
            # Convert to eV / Ang
            g_qklnn *= units.Hartree / units.Bohr
        else:
            # Multiply prefactor sqrt(hbar / 2 * M * omega) in units of Bohr
            amu = units._amu  # atomic mass unit
            me = units._me  # electron mass
            g_qklnn /= np.sqrt(2 * amu / me / units.Hartree * \
                               omega_ql[:, np.newaxis, :, np.newaxis, np.newaxis])
            # Convert to eV
            g_qklnn *= units.Hartree

        # Return couplings in eV (or eV / Ang)
        return g_qklnn

    def fourier_filter(self, V1t_xG, components='normal', criteria=1):
        """Fourier filter atomic gradients of the effective potential.

        Parameters
        ----------
        V1t_xG: ndarray
            Array representation of atomic gradients of the effective potential
            in the supercell grid.
        components: str
            Fourier components to filter out (``normal`` or ``umklapp``).
            
        """

        assert components in ['normal', 'umklapp']
        # Grid shape
        shape = V1t_xG.shape[-3:]

        # Primitive unit cells in Bohr/Bohr^-1
        cell_cv = self.atoms.get_cell() / units.Bohr
        reci_vc = 2 * pi * la.inv(cell_cv)
        norm_c = np.sqrt(np.sum(reci_vc**2, axis=0))
        # Periodic BC array
        pbc_c = np.array(self.atoms.get_pbc(), dtype=bool)

        # Supercell atoms and cell
        atoms_N = self.atoms * self.N_c
        supercell_cv = atoms_N.get_cell() / units.Bohr

        # q-grid in units of the grid spacing (FFT ordering)
        q_cG = np.indices(shape).reshape(3, -1)
        q_c = np.array(shape)[:, np.newaxis]
        q_cG += q_c // 2
        q_cG %= q_c
        q_cG -= q_c // 2

        # Locate q-points inside the Brillouin zone
        if criteria == 0:
            # Works for all cases
            # Grid spacing in direction of reciprocal lattice vectors
            h_c = np.sqrt(np.sum((2 * pi * la.inv(supercell_cv))**2, axis=0))
            # XXX Why does a "*=" operation on q_cG not work here ??
            q1_cG = q_cG * h_c[:, np.newaxis] / (norm_c[:, np.newaxis] / 2)
            mask_G = np.ones(np.prod(shape), dtype=bool)
            for i, pbc in enumerate(pbc_c):
                if not pbc:
                    continue
                mask_G &= (-1. < q1_cG[i]) & (q1_cG[i] <= 1.)
        else:
            # 2D hexagonal lattice
            # Projection of q points onto the periodic directions. Only in
            # these directions do normal and umklapp processees make sense.
            q_vG = np.dot(q_cG[pbc_c].T,
                          2 * pi * la.inv(supercell_cv).T[pbc_c]).T.copy()
            # Parametrize the BZ boundary in terms of the angle theta
            theta_G = np.arctan2(q_vG[1], q_vG[0]) % (pi / 3)
            phi_G = pi / 6 - np.abs(theta_G)
            qmax_G = norm_c[0] / 2 / np.cos(phi_G)
            norm_G = np.sqrt(np.sum(q_vG**2, axis=0))
            # Includes point on BZ boundary with +1e-2
            mask_G = (norm_G <= qmax_G + 1e-2
                      )  # & (q_vG[1] < (norm_c[0] / 2 - 1e-3))

        if components != 'normal':
            mask_G = ~mask_G

        # Reshape to grid shape
        mask_G.shape = shape

        for V1t_G in V1t_xG:
            # Fourier transform atomic gradient
            V1tq_G = fft.fftn(V1t_G)
            # Zero normal/umklapp components
            V1tq_G[mask_G] = 0.0
            # Fourier transform back
            V1t_G[:] = fft.ifftn(V1tq_G).real

    def calculate_gradient(self):
        """Calculate gradient of effective potential and projector coefs.

        This function loads the generated pickle files and calculates
        finite-difference derivatives.

        """

        # Array and dict for finite difference derivatives
        V1t_xG = []
        dH1_xasp = []

        x = 0
        for a in self.indices:
            for v in range(3):
                name = '%s.%d%s' % (self.name, a, 'xyz'[v])
                # Potential and atomic density matrix for atomic displacement
                try:
                    Vtm_G, dHm_asp = pickle.load(open(name + '-.pckl'))
                    Vtp_G, dHp_asp = pickle.load(open(name + '+.pckl'))
                except (IOError, EOFError):
                    raise IOError, "%s(-/+).pckl" % name

                # FD derivatives in Hartree / Bohr
                V1t_G = (Vtp_G - Vtm_G) / (2 * self.delta / units.Bohr)
                V1t_xG.append(V1t_G)

                dH1_asp = {}
                for atom in dHm_asp.keys():
                    dH1_asp[atom] = (dHp_asp[atom] - dHm_asp[atom]) / \
                                    (2 * self.delta / units.Bohr)
                dH1_xasp.append(dH1_asp)
                x += 1

        return np.array(V1t_xG), dH1_xasp

    def calculate_dP_aqvMi(self, wfs):
        """Overlap between LCAO basis functions and gradient of projectors.

        Only the gradient wrt the atomic positions in the reference cell is
        computed.

        """

        nao = wfs.setups.nao
        nq = len(wfs.ibzk_qc)
        atoms = [self.atoms[i] for i in self.indices]

        # Derivatives in reference cell
        dP_aqvMi = {}
        for atom, setup in zip(atoms, wfs.setups):
            a = atom.index
            dP_aqvMi[a] = np.zeros((nq, 3, nao, setup.ni), wfs.dtype)

        # Calculate overlap between basis function and gradient of projectors
        # NOTE: the derivative is calculated wrt the atomic position and not
        # the real-space coordinate
        calc = TwoCenterIntegralCalculator(wfs.ibzk_qc, derivative=True)
        expansions = ManySiteDictionaryWrapper(wfs.tci.P_expansions, dP_aqvMi)
        calc.calculate(wfs.tci.atompairs, [expansions], [dP_aqvMi])

        # Extract derivatives in the reference unit cell
        # dP_aqvMi = {}
        # for atom in self.atoms:
        #     dP_aqvMi[atom.index] = dPall_aqvMi[atom.index]

        return dP_aqvMi
Exemplo n.º 6
0
class ElectronPhononCoupling(Displacement):
    """Class for calculating the electron-phonon coupling in an LCAO basis.

    The derivative of the effective potential wrt atomic displacements is
    obtained from a finite difference approximation to the derivative by doing
    a self-consistent calculation for atomic displacements in the +/-
    directions. These calculations are carried out in the ``run`` member
    function.

    The subsequent calculation of the coupling matrix in the basis of atomic
    orbitals (or Bloch-sums hereof for periodic systems) is handled by the
    ``calculate_matrix`` member function. 
   
    """

    def __init__(self, atoms, calc=None, supercell=(1, 1, 1), name='elph',
                 delta=0.01, phonons=None):
        """Initialize with base class args and kwargs.

        Parameters
        ----------
        atoms: Atoms object
            The atoms to work on.
        calc: Calculator
            Calculator for the supercell calculation.
        supercell: tuple
            Size of supercell given by the number of repetitions (l, m, n) of
            the small unit cell in each direction.
        name: str
            Name to use for files (default: 'elph').
        delta: float
            Magnitude of displacements.
        phonons: Phonons object
            If provided, the ``__call__`` member function of the ``Phonons``
            class in ASE will be called in the ``__call__`` member function of
            this class.
            
        """

        # Init base class and make the center cell in the supercell the
        # reference cell 
        Displacement.__init__(self, atoms, calc=calc, supercell=supercell,
                              name=name, delta=delta, refcell='center')

        # Store ``Phonons`` object in attribute
        self.phonons = phonons
        # Log
        self.set_log()
        # LCAO calculator
        self.calc_lcao = None
        # Supercell matrix
        self.g_xNNMM = None
        
    def __call__(self, atoms_N):
        """Extract effective potential and projector coefficients."""

        # Do calculation
        atoms_N.get_potential_energy()

        # Call phonons class if provided
        if self.phonons is not None:
            forces = self.phonons.__call__(atoms_N)
            
        # Get calculator
        calc = atoms_N.get_calculator()

        # Effective potential (in Hartree) and projector coefficients
        Vt_G = calc.hamiltonian.vt_sG[0]
        Vt_G = calc.wfs.gd.collect(Vt_G, broadcast=True)
        dH_asp = calc.hamiltonian.dH_asp

        setups = calc.wfs.setups
        nspins = calc.wfs.nspins
        gd_comm = calc.wfs.gd.comm
                
        dH_all_asp = {}
        for a, setup in enumerate(setups):
            ni = setup.ni
            nii = ni * (ni + 1) // 2
            dH_tmp_sp = np.zeros((nspins, nii))
            if a in dH_asp:
                dH_tmp_sp[:] = dH_asp[a]
            gd_comm.sum(dH_tmp_sp)
            dH_all_asp[a] = dH_tmp_sp

        return Vt_G, dH_all_asp

    def set_lcao_calculator(self, calc):
        """Set LCAO calculator for the calculation of the supercell matrix."""

        # Add parameter checks here
        # - check that gamma
        # - check that no symmetries are used
        # - ...
        parameters = calc.input_parameters
        assert parameters['mode'] == 'lcao', "LCAO mode required."
        assert parameters['usesymm'] != True, "Symmetries not supported."
        
        self.calc_lcao = calc

    def set_basis_info(self, *args):
        """Store lcao basis info for atoms in reference cell in attribute.

        Parameters
        ----------
        args: tuple
            If the LCAO calculator is not available (e.g. if the supercell is
            loaded from file), the ``load_supercell_matrix`` member function
            provides the required info as arguments.
            
        """

        if len(args) == 0:
            calc = self.calc_lcao
            setups = calc.wfs.setups
            bfs = calc.wfs.basis_functions
            nao_a = [setups[a].nao for a in range(len(self.atoms))]
            M_a = [bfs.M_a[a] for a in range(len(self.atoms))]
        else:
            M_a = args[0]
            nao_a = args[1]
            
        self.basis_info = {'M_a': M_a,
                           'niAO_a': nao_a} # XXX niAO -> nao

    def set_log(self, log=None):
        """Set output log."""

        if log is None:
            self.timer = nulltimer
        elif log == '-':
            self.timer = StepTimer(name='elph')
        else:
            self.timer = StepTimer(name='elph', out=open(log, 'w'))

    def calculate_supercell_matrix(self, dump=0, name=None, filter=None,
                                   include_pseudo=True, atoms=None):
        """Calculate matrix elements of the el-ph coupling in the LCAO basis.

        This function calculates the matrix elements between LCAOs and local
        atomic gradients of the effective potential. The matrix elements are
        calculated for the supercell used to obtain finite-difference
        approximations to the derivatives of the effective potential wrt to
        atomic displacements.

        Parameters
        ----------
        dump: int
            Dump supercell matrix to pickle file (default: 0).

            0: Supercell matrix not saved

            1: Supercell matrix saved in a single pickle file.

            2: Dump matrix for different gradients in separate files. Useful
               for large systems where the total array gets too large for a
               single pickle file.

        name: string
            User specified name of the generated pickle file(s). If not
            provided, the string in the ``name`` attribute is used.
        filter: str
            Fourier filter atomic gradients of the effective potential. The
            specified components (``normal`` or ``umklapp``) are removed
            (default: None).
        include_pseudo: bool
            Include the contribution from the psedupotential in the atomic
            gradients. If ``False``, only the gradient of the effective
            potential is included (default: True).
        atoms: Atoms object
            Calculate supercell for an ``Atoms`` object different from the one
            provided in the ``__init__`` method (WARNING, NOT working!).
            
        """

        assert self.calc_lcao is not None, "Set LCAO calculator"
            
        # Supercell atoms
        if atoms is None:
            atoms_N = self.atoms * self.N_c
        else:
            atoms_N = atoms
        
        # Initialize calculator if required and extract useful quantities
        calc = self.calc_lcao
        if not hasattr(calc.wfs, 'S_qMM'):
            calc.initialize(atoms_N)
            calc.initialize_positions(atoms_N)
        self.set_basis_info()
        basis = calc.input_parameters['basis']
            
        # Extract useful objects from the calculator
        wfs = calc.wfs
        gd = calc.wfs.gd
        kd = calc.wfs.kd
        kpt_u = wfs.kpt_u
        setups = wfs.setups
        nao = setups.nao
        bfs = wfs.basis_functions
        dtype = wfs.dtype
        spin = 0 # XXX
        
        # If gamma calculation, overlap with neighboring cell cannot be removed
        if kd.gamma:
            print "WARNING: Gamma-point calculation."
        else:
            # Bloch to real-space converter
            tb = TightBinding(atoms_N, calc)

        self.timer.write_now("Calculating supercell matrix")

        self.timer.write_now("Calculating real-space gradients")        
        # Calculate finite-difference gradients (in Hartree / Bohr)
        V1t_xG, dH1_xasp = self.calculate_gradient()
        self.timer.write_now("Finished real-space gradients")

        # Fourier filter the atomic gradients of the effective potential
        if filter is not None:
            self.timer.write_now("Fourier filtering gradients")
            V1_xG = V1t_xG.copy()
            self.fourier_filter(V1t_xG, components=filter)
            self.timer.write_now("Finished Fourier filtering")

        # For the contribution from the derivative of the projectors
        dP_aqvMi = self.calculate_dP_aqvMi(wfs)
        # Equilibrium atomic Hamiltonian matrix (projector coefficients)
        dH_asp = pickle.load(open(self.name + '.eq.pckl'))[1]
        
        # Check that the grid is the same as in the calculator
        assert np.all(V1t_xG.shape[-3:] == (gd.N_c + gd.pbc_c - 1)), \
               "Mismatch in grids."

        # Calculate < i k | grad H | j k >, i.e. matrix elements in Bloch basis
        # List for supercell matrices;
        g_xNNMM = []
        self.timer.write_now("Calculating gradient of PAW Hamiltonian")

        # Do each cartesian component separately
        for i, a in enumerate(self.indices):
            for v in range(3):

                # Corresponding array index
                x = 3 * i + v
                V1t_G = V1t_xG[x]
                self.timer.write_now("%s-gradient of atom %u" % 
                                     (['x','y','z'][v], a))

                # Array for different k-point components
                g_qMM = np.zeros((len(kpt_u), nao, nao), dtype)
                
                # 1) Gradient of effective potential
                self.timer.write_now("Starting gradient of effective potential")
                for kpt in kpt_u:
                    # Matrix elements
                    geff_MM = np.zeros((nao, nao), dtype)
                    bfs.calculate_potential_matrix(V1t_G, geff_MM, q=kpt.q)
                    tri2full(geff_MM, 'L')
                    # Insert in array
                    g_qMM[kpt.q] += geff_MM
                
                self.timer.write_now("Finished gradient of effective potential")

                if include_pseudo:
                    self.timer.write_now("Starting gradient of pseudo part")
                
                    # 2) Gradient of non-local part (projectors)
                    self.timer.write_now("Starting gradient of dH^a")
                    P_aqMi = calc.wfs.P_aqMi
                    # 2a) dH^a part has contributions from all other atoms
                    for kpt in kpt_u:
                        # Matrix elements
                        gp_MM = np.zeros((nao, nao), dtype)
                        dH1_asp = dH1_xasp[x]
                        for a_, dH1_sp in dH1_asp.items():
                            dH1_ii = unpack2(dH1_sp[spin])
                            gp_MM += np.dot(P_aqMi[a_][kpt.q], np.dot(dH1_ii,
                                            P_aqMi[a_][kpt.q].T.conjugate()))
                        g_qMM[kpt.q] += gp_MM
                    self.timer.write_now("Finished gradient of dH^a")
                    
                    self.timer.write_now("Starting gradient of projectors")
                    # 2b) dP^a part has only contributions from the same atoms
                    dP_qvMi = dP_aqvMi[a]
                    dH_ii = unpack2(dH_asp[a][spin])
                    for kpt in kpt_u:
                        #XXX Sort out the sign here; conclusion -> sign = +1 !
                        P1HP_MM = +1 * np.dot(dP_qvMi[kpt.q][v], np.dot(dH_ii,
                                              P_aqMi[a][kpt.q].T.conjugate()))
                        # Matrix elements
                        gp_MM = P1HP_MM + P1HP_MM.T.conjugate()
                        g_qMM[kpt.q] += gp_MM
                    self.timer.write_now("Finished gradient of projectors")
                    self.timer.write_now("Finished gradient of pseudo part")
                
                # Extract R_c=(0, 0, 0) block by Fourier transforming
                if kd.gamma or kd.N_c is None:
                    g_MM = g_qMM[0]
                else:
                    # Convert to array
                    g_MM = tb.bloch_to_real_space(g_qMM, R_c=(0, 0, 0))[0]
                
                # Reshape to global unit cell indices
                N = np.prod(self.N_c)
                # Number of basis function in the primitive cell
                assert (nao % N) == 0, "Alarm ...!"
                nao_cell = nao / N
                g_NMNM = g_MM.reshape((N, nao_cell, N, nao_cell))
                g_NNMM = g_NMNM.swapaxes(1, 2).copy()
                self.timer.write_now("Finished supercell matrix")
                
                if dump != 2:
                    g_xNNMM.append(g_NNMM)
                else:
                    if name is not None:
                        fname = '%s.supercell_matrix_x_%2.2u.%s.pckl' % (name, x, basis)
                    else:
                        fname = self.name + \
                                '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis)
                    if kd.comm.rank == 0:
                        fd = open(fname, 'w')
                        M_a = self.basis_info['M_a']
                        nao_a = self.basis_info['niAO_a']
                        pickle.dump((g_NNMM, M_a, nao_a), fd, 2)
                        fd.close()
                    
        self.timer.write_now("Finished gradient of PAW Hamiltonian")
        
        if dump != 2:
            # Collect gradients in one array
            self.g_xNNMM = np.array(g_xNNMM)
            
            # Dump to pickle file using binary mode together with basis info
            if dump and kd.comm.rank == 0:
                if name is not None:
                    fname = '%s.supercell_matrix.%s.pckl' % (name, basis)
                else:
                    fname = self.name + '.supercell_matrix.%s.pckl' % basis
                fd = open(fname, 'w')
                M_a = self.basis_info['M_a']
                nao_a = self.basis_info['nao_a']                
                pickle.dump((self.g_xNNMM, M_a, nao_a), fd, 2)
                fd.close()

    def load_supercell_matrix(self, basis=None, name=None, multiple=False):
        """Load supercell matrix from pickle file.

        Parameters
        ----------
        basis: string
            String specifying the LCAO basis used to calculate the supercell
            matrix, e.g. 'dz(dzp)'.
        name: string
            User specified name of the pickle file.
        multiple: bool
            Load each derivative from individual files.
            
        """

        assert (basis is not None) or (name is not None), \
               "Provide basis or name."

        if self.g_xNNMM is not None:
            self.g_xNNMM = None
            
        if not multiple:
            # File name
            if name is not None:
                fname = name
            else:
                fname = self.name + '.supercell_matrix.%s.pckl' % basis
            fd = open(fname)
            self.g_xNNMM, M_a, nao_a = pickle.load(fd)
            fd.close()
        else:
            g_xNNMM = []
            for x in range(len(self.indices)*3):
                if name is not None:
                    fname = name
                else:
                    fname = self.name + \
                            '.supercell_matrix_x_%2.2u.%s.pckl' % (x, basis)
                fd = open(fname, 'r')
                g_NNMM, M_a, nao_a = pickle.load(fd)
                fd.close()
                g_xNNMM.append(g_NNMM)
            self.g_xNNMM = np.array(g_xNNMM)

        self.set_basis_info(M_a, nao_a)
                                  
    def apply_cutoff(self, cutmax=None, cutmin=None):
        """Zero matrix element inside/beyond the specified cutoffs.

        Parameters
        ----------
        cutmax: float
            Zero matrix elements for basis functions with a distance to the
            atomic gradient that is larger than the cutoff.
        cutmin: float
            Zero matrix elements where both basis functions have distances to
            the atomic gradient that is smaller than the cutoff.

        """

        if cutmax is not None:
            cutmax = float(cutmax)
        if cutmin is not None:
            cutmin = float(cutmin)
            
        # Reference to supercell matrix attribute
        g_xNNMM = self.g_xNNMM
        
        # Number of atoms and primitive cells
        N_atoms = len(self.indices)
        N = np.prod(self.N_c)
        nao = g_xNNMM.shape[-1]
        
        # Reshape array
        g_avNNMM = g_xNNMM.reshape(N_atoms, 3, N, N, nao, nao)
        
        # Make slices for orbitals on atoms
        M_a = self.basis_info['M_a']
        nao_a = self.basis_info['niAO_a']
        slice_a = []
        for a in range(len(self.atoms)):
            start = M_a[a] ;
            stop = start + nao_a[a]
            s = slice(start, stop)
            slice_a.append(s)
            
        # Lattice vectors
        R_cN = self.lattice_vectors()

        # Unit cell vectors
        cell_vc = self.atoms.cell.transpose()
        # Atomic positions in reference cell
        pos_av = self.atoms.get_positions()

        # Create a mask array to zero the relevant matrix elements
        if cutmin is not None:
            mask_avNNMM = np.zeros(g_avNNMM.shape, dtype=bool)
            
        # Zero elements where one of the basis orbitals has a distance to atoms
        # (atomic gradients) in the reference cell larger than the cutoff
        for n in range(N):
            # Lattice vector to cell
            R_v = np.dot(cell_vc, R_cN[:, n])
            # Atomic positions in cell
            posn_av = pos_av + R_v
            for i, a in enumerate(self.indices):
                # Atomic distances wrt to the position of the gradient
                dist_a = np.sqrt(np.sum((pos_av[a] - posn_av)**2, axis=-1))

                if cutmax is not None:
                    # Atoms indices where the distance is larger than the max
                    # cufoff 
                    j_a = np.where(dist_a > cutmax)[0]
                    # Zero elements
                    for j in j_a:
                        g_avNNMM[a, :, n, :, slice_a[j], :] = 0.0
                        g_avNNMM[a, :, :, n, :, slice_a[j]] = 0.0

                if cutmin is not None:
                    # Atoms indices where the distance is larger than the min
                    # cufoff 
                    j_a = np.where(dist_a > cutmin)[0]
                    # Update mask to keep elements where one LCAO is outside
                    # the min cutoff
                    for j in j_a:
                        mask_avNNMM[a, :, n, :, slice_a[j], :] = True
                        mask_avNNMM[a, :, :, n, :, slice_a[j]] = True

        # Zero elements where both LCAOs are located within the min cutoff
        if cutmin is not None:
            g_avNNMM[~mask_avNNMM] = 0.0
                        
    def lcao_matrix(self, u_l, omega_l):
        """Calculate the el-ph coupling in the electronic LCAO basis.
        
        For now, only works for Gamma-point phonons.
        
        Parameters
        ----------
        u_l: ndarray
            Mass-scaled polarization vectors (in units of 1 / sqrt(amu)) of the
            phonons.
        omega_l: ndarray
            Vibrational frequencies in eV. 
            
        """

        # Supercell matrix (Hartree / Bohr)
        assert self.g_xNNMM is not None, "Load supercell matrix."        
        assert self.g_xNNMM.shape[1:3] == (1, 1)
        g_xMM = self.g_xNNMM[:, 0, 0, :, :]
        # Number of atomic orbitals
        nao = g_xMM.shape[-1]
        # Number of phonon modes
        nmodes = u_l.shape[0]

        # 
        u_lx = u_l.reshape(nmodes, 3 * len(self.atoms))
        g_lMM = np.dot(u_lx, g_xMM.transpose(1, 0, 2))

        # Multiply prefactor sqrt(hbar / 2 * M * omega) in units of Bohr
        amu = units._amu # atomic mass unit
        me = units._me   # electron mass
        g_lMM /= np.sqrt(2 * amu / me / units.Hartree * \
                         omega_l[:, np.newaxis, np.newaxis])
        # Convert to eV
        g_lMM *= units.Hartree
                
        return g_lMM
                            
    def bloch_matrix(self, kpts, qpts, c_kn, u_ql, omega_ql=None, kpts_from=None):
        """Calculate el-ph coupling in the Bloch basis for the electrons.

        This function calculates the electron-phonon coupling between the
        specified Bloch states, i.e.::

                      ______ 
            mnl      / hbar               ^
           g    =   /-------  < m k + q | e  . grad V  | n k >
            kq    \/ 2 M w                 ql        q
                          ql

        In case the ``omega_ql`` keyword argument is not given, the bare matrix
        element (in units of eV / Ang) without the sqrt prefactor is returned. 
        
        Parameters
        ----------
        kpts: ndarray or tuple.
            k-vectors of the Bloch states. When a tuple of integers is given, a
            Monkhorst-Pack grid with the specified number of k-points along the
            directions of the reciprocal lattice vectors is generated.
        qpts: ndarray or tuple.
            q-vectors of the phonons.
        c_kn: ndarray
            Expansion coefficients for the Bloch states. The ordering must be
            the same as in the ``kpts`` argument.
        u_ql: ndarray
            Mass-scaled polarization vectors (in units of 1 / sqrt(amu)) of the
            phonons. Again, the ordering must be the same as in the
            corresponding ``qpts`` argument.
        omega_ql: ndarray
            Vibrational frequencies in eV. 
        kpts_from: list of ints or int
            Calculate only the matrix element for the k-vectors specified by
            their index in the ``kpts`` argument (default: all).

        In short, phonon frequencies and mode vectors must be given in ase units.

        """

        assert self.g_xNNMM is not None, "Load supercell matrix."
        assert len(c_kn.shape) == 3
        assert len(u_ql.shape) == 4
        if omega_ql is not None:
            assert np.all(u_ql.shape[:2] == omega_ql.shape[:2])

        # Translate k-points into 1. BZ (required by ``find_k_plus_q``` member
        # function of the ```KPointDescriptor``).
        if isinstance(kpts, np.ndarray):
            assert kpts.shape[1] == 3, "kpts_kc array must be given"
            # XXX This does not seem to cause problems!
            kpts -= kpts.round()
            
        # Use the KPointDescriptor to keep track of the k and q-vectors
        kd_kpts = KPointDescriptor(kpts)
        kd_qpts = KPointDescriptor(qpts)
        # Check that number of k- and q-points agree with the number of Bloch
        # functions and polarization vectors
        assert kd_kpts.nbzkpts == len(c_kn)
        assert kd_qpts.nbzkpts == len(u_ql)
        
        # Include all k-point per default
        if kpts_from is None:
            kpts_kc = kd_kpts.bzk_kc
            kpts_k = range(kd_kpts.nbzkpts)
        else:
            kpts_kc = kd_kpts.bzk_kc[kpts_from]
            if isinstance(kpts_from, int):
                kpts_k = list([kpts_from])
            else:
                kpts_k = list(kpts_from)

        # Supercell matrix (real matrix in Hartree / Bohr)
        g_xNNMM = self.g_xNNMM
        
        # Number of phonon modes and electronic bands
        nmodes = u_ql.shape[1]
        nbands = c_kn.shape[1]
        # Number of atoms displacements and basis functions
        ndisp = np.prod(u_ql.shape[2:])
        assert ndisp == (3 * len(self.indices))
        nao = c_kn.shape[2]
        assert ndisp == g_xNNMM.shape[0]
        assert nao == g_xNNMM.shape[-1]
        
        # Lattice vectors
        R_cN = self.lattice_vectors()
        # Number of unit cell in supercell
        N = np.prod(self.N_c)
        
        # Allocate array for couplings
        g_qklnn = np.zeros((kd_qpts.nbzkpts, len(kpts_kc), nmodes,
                            nbands, nbands), dtype=complex)
       
        self.timer.write_now("Calculating coupling matrix elements")
        for q, q_c in enumerate(kd_qpts.bzk_kc):

            # Find indices of k+q for the k-points
            kplusq_k = kd_kpts.find_k_plus_q(q_c, kpts_k=kpts_k)

            # Here, ``i`` is counting from 0 and ``k`` is the global index of
            # the k-point 
            for i, (k, k_c) in enumerate(zip(kpts_k, kpts_kc)):

                # Check the wave vectors (adapted to the ``KPointDescriptor`` class)
                kplusq_c = k_c + q_c
                kplusq_c -= kplusq_c.round()
                assert np.allclose(kplusq_c, kd_kpts.bzk_kc[kplusq_k[i]] ), \
                       (i, k, k_c, q_c, kd_kpts.bzk_kc[kplusq_k[i]])

                # Allocate array
                g_xMM = np.zeros((ndisp, nao, nao), dtype=complex)

                # Multiply phase factors
                for m in range(N):
                    for n in range(N):
                        Rm_c = R_cN[:, m]
                        Rn_c = R_cN[:, n]
                        phase = np.exp(2.j * pi * (np.dot(k_c, Rm_c - Rn_c)
                                                   + np.dot(q_c, Rm_c)))
                        # Sum contributions from different cells
                        g_xMM += g_xNNMM[:, m, n, :, :] * phase
                        
                # LCAO coefficient for Bloch states
                ck_nM = c_kn[k]
                ckplusq_nM = c_kn[kplusq_k[i]]
                # Mass scaled polarization vectors
                u_lx = u_ql[q].reshape(nmodes, 3 * len(self.atoms))

                g_nxn = np.dot(ckplusq_nM.conj(), np.dot(g_xMM, ck_nM.T))
                g_lnn = np.dot(u_lx, g_nxn)
                
                # Insert value
                g_qklnn[q, i] = g_lnn

                # XXX Temp
                if np.all(q_c == 0.0):
                    # These should be real
                    print g_qklnn[q].imag.min(), g_qklnn[q].imag.max()
                    
        self.timer.write_now("Finished calculation of coupling matrix elements")
                                                
        # Return the bare matrix element if frequencies are not given
        if omega_ql is None:
            # Convert to eV / Ang
            g_qklnn *= units.Hartree / units.Bohr
        else:
            # Multiply prefactor sqrt(hbar / 2 * M * omega) in units of Bohr
            amu = units._amu # atomic mass unit
            me = units._me   # electron mass
            g_qklnn /= np.sqrt(2 * amu / me / units.Hartree * \
                               omega_ql[:, np.newaxis, :, np.newaxis, np.newaxis])
            # Convert to eV
            g_qklnn *= units.Hartree
            
        # Return couplings in eV (or eV / Ang)
        return g_qklnn

    def fourier_filter(self, V1t_xG, components='normal', criteria=1):
        """Fourier filter atomic gradients of the effective potential.

        Parameters
        ----------
        V1t_xG: ndarray
            Array representation of atomic gradients of the effective potential
            in the supercell grid.
        components: str
            Fourier components to filter out (``normal`` or ``umklapp``).
            
        """

        assert components in ['normal', 'umklapp']
        # Grid shape
        shape = V1t_xG.shape[-3:]
        
        # Primitive unit cells in Bohr/Bohr^-1
        cell_cv = self.atoms.get_cell() / units.Bohr
        reci_vc = 2 * pi * la.inv(cell_cv)
        norm_c = np.sqrt(np.sum(reci_vc**2, axis=0))
        # Periodic BC array
        pbc_c = np.array(self.atoms.get_pbc(), dtype=bool)
        
        # Supercell atoms and cell
        atoms_N = self.atoms * self.N_c
        supercell_cv = atoms_N.get_cell() / units.Bohr

        # q-grid in units of the grid spacing (FFT ordering)
        q_cG = np.indices(shape).reshape(3, -1)
        q_c = np.array(shape)[:, np.newaxis]
        q_cG += q_c // 2
        q_cG %= q_c
        q_cG -= q_c // 2

        # Locate q-points inside the Brillouin zone
        if criteria == 0:
            # Works for all cases
            # Grid spacing in direction of reciprocal lattice vectors
            h_c = np.sqrt(np.sum((2 * pi * la.inv(supercell_cv))**2, axis=0))
            # XXX Why does a "*=" operation on q_cG not work here ??
            q1_cG = q_cG * h_c[:, np.newaxis] / (norm_c[:, np.newaxis] / 2)
            mask_G = np.ones(np.prod(shape), dtype=bool)
            for i, pbc in enumerate(pbc_c):
                if not pbc:
                    continue
                mask_G &= (-1. < q1_cG[i]) & (q1_cG[i] <= 1.)
        else:
            # 2D hexagonal lattice
            # Projection of q points onto the periodic directions. Only in
            # these directions do normal and umklapp processees make sense.
            q_vG = np.dot(q_cG[pbc_c].T,
                          2 * pi * la.inv(supercell_cv).T[pbc_c]).T.copy()
            # Parametrize the BZ boundary in terms of the angle theta
            theta_G = np.arctan2(q_vG[1], q_vG[0]) % (pi / 3)
            phi_G = pi / 6 - np.abs(theta_G)
            qmax_G = norm_c[0] / 2 / np.cos(phi_G)
            norm_G = np.sqrt(np.sum(q_vG**2, axis=0))
            # Includes point on BZ boundary with +1e-2
            mask_G = (norm_G <= qmax_G + 1e-2) # & (q_vG[1] < (norm_c[0] / 2 - 1e-3))

        if components != 'normal':
            mask_G = ~mask_G
            
        # Reshape to grid shape
        mask_G.shape = shape

        for V1t_G in V1t_xG:
            # Fourier transform atomic gradient
            V1tq_G = fft.fftn(V1t_G)
            # Zero normal/umklapp components
            V1tq_G[mask_G] = 0.0
            # Fourier transform back
            V1t_G[:] = fft.ifftn(V1tq_G).real
            
    def calculate_gradient(self):
        """Calculate gradient of effective potential and projector coefs.

        This function loads the generated pickle files and calculates
        finite-difference derivatives.

        """

        # Array and dict for finite difference derivatives
        V1t_xG = []
        dH1_xasp = []

        x = 0
        for a in self.indices:
            for v in range(3):
                name = '%s.%d%s' % (self.name, a, 'xyz'[v])
                # Potential and atomic density matrix for atomic displacement
                try:
                    Vtm_G, dHm_asp = pickle.load(open(name + '-.pckl'))
                    Vtp_G, dHp_asp = pickle.load(open(name + '+.pckl'))
                except (IOError, EOFError):
                    raise IOError, "%s(-/+).pckl" % name
                
                # FD derivatives in Hartree / Bohr
                V1t_G = (Vtp_G - Vtm_G) / (2 * self.delta / units.Bohr)
                V1t_xG.append(V1t_G)

                dH1_asp = {}
                for atom in dHm_asp.keys():
                    dH1_asp[atom] = (dHp_asp[atom] - dHm_asp[atom]) / \
                                    (2 * self.delta / units.Bohr)
                dH1_xasp.append(dH1_asp)
                x += 1
        
        return np.array(V1t_xG), dH1_xasp
        
    def calculate_dP_aqvMi(self, wfs):
        """Overlap between LCAO basis functions and gradient of projectors.

        Only the gradient wrt the atomic positions in the reference cell is
        computed.

        """
        
        nao = wfs.setups.nao
        nq = len(wfs.ibzk_qc)
        atoms = [self.atoms[i] for i in self.indices]
        
        # Derivatives in reference cell
        dP_aqvMi = {}
        for atom, setup in zip(atoms, wfs.setups):
            a = atom.index
            dP_aqvMi[a] = np.zeros((nq, 3, nao, setup.ni), wfs.dtype)

        # Calculate overlap between basis function and gradient of projectors
        # NOTE: the derivative is calculated wrt the atomic position and not
        # the real-space coordinate
        calc = TwoCenterIntegralCalculator(wfs.ibzk_qc, derivative=True)
        expansions = ManySiteDictionaryWrapper(wfs.tci.P_expansions, dP_aqvMi)
        calc.calculate(wfs.tci.atompairs, [expansions], [dP_aqvMi])

        # Extract derivatives in the reference unit cell
        # dP_aqvMi = {}
        # for atom in self.atoms:
        #     dP_aqvMi[atom.index] = dPall_aqvMi[atom.index]
            
        return dP_aqvMi