示例#1
0
    def _calc_recip(self, system):
        """
        Perform the reciprocal space summation. Uses the fastest non mesh-based
        method described as given by equation (16) in
        https://doi.org/10.1016/0010-4655(96)00016-1

        The term G=0 is neglected, even if the system has nonzero charge.
        Physically this would mean that we are adding a constant background
        charge to make the cell charge neutral.

        Args:
            system (:class:`ase.Atoms` | :class:`.System`): Input system.

        Returns:
            np.ndarray(): A 2D matrix containing the real space terms for each
            i,j pair.
        """
        n_atoms = self.n_atoms
        erecip = np.zeros((n_atoms, n_atoms), dtype=np.float)
        coords = system.get_positions()

        # Get the reciprocal lattice points within the reciprocal space cutoff
        rcp_latt = 2*np.pi*system.get_reciprocal_cell()
        rcp_latt = Lattice(rcp_latt)
        recip_nn = rcp_latt.get_points_in_sphere([[0, 0, 0]], [0, 0, 0],
                                                 self.gcut)

        # Ignore the terms with G=0.
        frac_coords = [fcoords for (fcoords, dist, i) in recip_nn if dist != 0]

        gs = rcp_latt.get_cartesian_coords(frac_coords)
        g2s = np.sum(gs ** 2, 1)
        expvals = np.exp(-g2s / (4 * self.a_squared))
        grs = np.sum(gs[:, None] * coords[None, :], 2)
        factors = np.divide(expvals, g2s)
        charges = self.q

        # Create array where q_2[i,j] is qi * qj
        qiqj = charges[None, :] * charges[:, None]

        for gr, factor in zip(grs, factors):

            # Uses the identity sin(x)+cos(x) = 2**0.5 sin(x + pi/4)
            m = (gr[None, :] + math.pi / 4) - gr[:, None]
            np.sin(m, m)
            m *= factor
            erecip += m

        erecip *= 4 * math.pi / self.volume * qiqj * 2 ** 0.5

        # The diagonal terms are divided by two
        diag = np.diag(erecip)/2
        np.fill_diagonal(erecip, diag)

        return erecip
示例#2
0
    def _calc_real(self, system):
        """Used to calculate the Ewald real-space sum.

        Corresponds to equation (5) in
        https://doi.org/10.1016/0010-4655(96)00016-1

        Args:
            system (:class:`ase.Atoms` | :class:`.System`): Input system.

        Returns:
            np.ndarray(): A 2D matrix containing the real space terms for each
            i,j pair.
        """
        fcoords = system.get_scaled_positions()
        coords = system.get_positions()
        n_atoms = len(system)
        ereal = np.zeros((n_atoms, n_atoms), dtype=np.float)
        lattice = Lattice(system.get_cell())

        # For each atom in the original cell, get the neighbours in the
        # infinite system within the real space cutoff and calculate the real
        # space portion of the Ewald sum.
        for i in range(n_atoms):

            # Get points that are within the real space cutoff
            nfcoords, rij, js = lattice.get_points_in_sphere(
                fcoords,
                coords[i],
                self.rcut,
                zip_results=False
            )
            # Remove the rii term, because a charge does not interact with
            # itself (but does interact with copies of itself).
            mask = rij > 1e-8
            js = js[mask]
            rij = rij[mask]
            nfcoords = nfcoords[mask]

            qi = self.q[i]
            qj = self.q[js]

            erfcval = erfc(self.a * rij)
            new_ereals = erfcval * qi * qj / rij

            # Insert new_ereals
            for k in range(n_atoms):
                ereal[k, i] = np.sum(new_ereals[js == k])

        # The diagonal terms are divided by two
        diag = np.diag(ereal)/2
        np.fill_diagonal(ereal, diag)

        return ereal